Loading libraries

library(GEOquery)
library(oligo)
# library(affy)
library(limma)
library(sva)
library(tidyverse)
library(factoextra)
library(pheatmap)
library(caret)
library(RColorBrewer)
# library(viridis)
library(ggsci)

Custom functions

# given a matrix, perform min-max scaling on its columns
min_max_mat <- function(mat){
  mat_rescaled <- apply(mat, 2, function(v){
    v_range <- range(v)
    names(v_range) <- c("minimum", "maximum")
    range_difference <- v_range["maximum"] - v_range["minimum"]
    rescaled <- (v - v_range["minimum"])/range_difference
    return(rescaled)
  })
  return(mat_rescaled)
}

Getting data from GEOquery

# geodata <- GEOquery::getGEO(GEO = "GSE76275", destdir = "./tempfiles")
# geodata <- GEOquery::getGEO(filename = "./tempfiles/GSE76275_series_matrix.txt.gz")
# saveRDS(geodata, "geodata.RDS")
geodata <- readRDS("geodata.RDS")
# mdata <- geodata %>% 
#   pluck(1) %>% 
#   phenoData() %>%
#   pData() %>% as_tibble()
# feature_data <- geodata %>% 
#   pluck(1) %>% 
#   featureData()
  
# write_csv(mdata, "raw_mdata.csv")
mdata <- read_csv("raw_mdata.csv")
Rows: 265 Columns: 69
── Column specification ──────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (62): title, geo_accession, status, submission_date, last_update_date, type, source_name_ch1, organism...
dbl  (6): channel_count, taxid_ch1, contact_zip/postal_code, data_row_count, age (years):ch1, body mass in...
lgl  (1): growth_protocol_ch1

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# saveRDS(feature_data, "featureData.RDS")
# feature_data <- readRDS("featureData.RDS")

Inspecting and cleaning the metadata

mdata %>% 
  glimpse()
Rows: 265
Columns: 69
$ title                                <chr> "S1-H10", "S1-H14", "S1-H19", "S1-H20B", "S1-H22", "S1-H27", "S…
$ geo_accession                        <chr> "GSM1974566", "GSM1974567", "GSM1974568", "GSM1974569", "GSM197…
$ status                               <chr> "Public on Dec 18 2015", "Public on Dec 18 2015", "Public on De…
$ submission_date                      <chr> "Dec 17 2015", "Dec 17 2015", "Dec 17 2015", "Dec 17 2015", "De…
$ last_update_date                     <chr> "Dec 18 2015", "Dec 18 2015", "Dec 18 2015", "Dec 18 2015", "De…
$ type                                 <chr> "RNA", "RNA", "RNA", "RNA", "RNA", "RNA", "RNA", "RNA", "RNA", …
$ channel_count                        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ source_name_ch1                      <chr> "Human breast cancer", "Human breast cancer", "Human breast can…
$ organism_ch1                         <chr> "Homo sapiens", "Homo sapiens", "Homo sapiens", "Homo sapiens",…
$ characteristics_ch1                  <chr> "tissue: Breast cancer", "tissue: Breast cancer", "tissue: Brea…
$ characteristics_ch1.1                <chr> "gender: Female", "age (years): 41", "age (years): 55", "age (y…
$ characteristics_ch1.2                <chr> "race: Caucasian", "gender: Female", "gender: Female", "gender:…
$ characteristics_ch1.3                <chr> "body mass index: 32", "race: Caucasian", "race: Caucasian", "r…
$ characteristics_ch1.4                <chr> "menopausal status: Post-Menopausal", "body mass index: 29", "h…
$ characteristics_ch1.5                <chr> "histology group: Infiltrating Ductal Carcinoma", "menopausal s…
$ characteristics_ch1.6                <chr> "histology: Infiltrating Ductal Carcinoma", "histology group: I…
$ characteristics_ch1.7                <chr> "ajcc stage (7th edition, 2010): T2N1M0", "histology: Infiltrat…
$ characteristics_ch1.8                <chr> "set: Validation TN", "ajcc stage (7th edition, 2010): T1N0M0",…
$ characteristics_ch1.9                <chr> "er: Negative", "set: Validation TN", "pr: Negative", "set: Val…
$ characteristics_ch1.10               <chr> "pr: Negative", "er: Negative", "her2: Negative", "er: Negative…
$ characteristics_ch1.11               <chr> "her2: Negative", "pr: Negative", "triple-negative status: TN",…
$ characteristics_ch1.12               <chr> "triple-negative status: TN", "her2: Negative", "tumor grade: P…
$ characteristics_ch1.13               <chr> "tumor size: 2 - 5 cm", "triple-negative status: TN", "tumor si…
$ characteristics_ch1.14               <chr> "positive nodes: 1 - 3", "tumor grade: Poorly Differentiated", …
$ characteristics_ch1.15               <chr> "metastases: No mets", "tumor size: <=2cm", "metastases: No met…
$ characteristics_ch1.16               <chr> "tnbc subtype: Mesenchymal (MES)", "positive nodes: 0", "tnbc s…
$ characteristics_ch1.17               <chr> NA, "metastases: No mets", NA, "metastases: No mets", NA, NA, N…
$ characteristics_ch1.18               <chr> NA, "tnbc subtype: Basal-Like Immune-Activated (BLIA)", NA, "tn…
$ treatment_protocol_ch1               <chr> "breast tumor samples, flash frozen", "breast tumor samples, fl…
$ growth_protocol_ch1                  <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ molecule_ch1                         <chr> "total RNA", "total RNA", "total RNA", "total RNA", "total RNA"…
$ extract_protocol_ch1                 <chr> "Trizol extraction of total RNA was performed according to the …
$ label_ch1                            <chr> "biotin", "biotin", "biotin", "biotin", "biotin", "biotin", "bi…
$ label_protocol_ch1                   <chr> "3' IVT express", "3' IVT express", "3' IVT express", "3' IVT e…
$ taxid_ch1                            <dbl> 9606, 9606, 9606, 9606, 9606, 9606, 9606, 9606, 9606, 9606, 960…
$ hyb_protocol                         <chr> "Standard Affymetix protocol for 3' IVT design", "Standard Affy…
$ scan_protocol                        <chr> "Scanner GCS3000 7G", "Scanner GCS3000 7G", "Scanner GCS3000 7G…
$ description                          <chr> "log2 gene expression data", "log2 gene expression data", "log2…
$ data_processing                      <chr> "The data were analyzed with  Affy package in R, RMA method", "…
$ data_processing.1                    <chr> "Expressions estimated using RMA signal method", "Expressions e…
$ platform_id                          <chr> "GPL570", "GPL570", "GPL570", "GPL570", "GPL570", "GPL570", "GP…
$ contact_name                         <chr> "Suzanne,,Fuqua", "Suzanne,,Fuqua", "Suzanne,,Fuqua", "Suzanne,…
$ contact_institute                    <chr> "Baylor College of Medicine", "Baylor College of Medicine", "Ba…
$ contact_address                      <chr> "1 Baylor Plaza", "1 Baylor Plaza", "1 Baylor Plaza", "1 Baylor…
$ contact_city                         <chr> "Houston", "Houston", "Houston", "Houston", "Houston", "Houston…
$ contact_state                        <chr> "Texas", "Texas", "Texas", "Texas", "Texas", "Texas", "Texas", …
$ `contact_zip/postal_code`            <dbl> 77030, 77030, 77030, 77030, 77030, 77030, 77030, 77030, 77030, …
$ contact_country                      <chr> "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", "USA", …
$ supplementary_file                   <chr> "ftp://ftp.ncbi.nlm.nih.gov/geo/samples/GSM1974nnn/GSM1974566/s…
$ data_row_count                       <dbl> 54675, 54675, 54675, 54675, 54675, 54675, 54675, 54675, 54675, …
$ `age (years):ch1`                    <dbl> NA, 41, 55, 55, 65, 40, 66, 57, NA, 55, 44, 78, 54, 71, 37, 51,…
$ `ajcc stage (7th edition, 2010):ch1` <chr> "T2N1M0", "T1N0M0", "T2N0M0", "T1cN0M0", "T2N2MX", "T1NXMX", "T…
$ `body mass index:ch1`                <dbl> 32, 29, NA, 31, 38, 22, 22, 38, 29, 32, 44, 28, 34, 32, 23, 23,…
$ `er:ch1`                             <chr> "Negative", "Negative", "Negative", "Negative", "Negative", "Ne…
$ `gender:ch1`                         <chr> "Female", "Female", "Female", "Female", "Female", "Female", "Fe…
$ `her2:ch1`                           <chr> "Negative", "Negative", "Negative", "Negative", "Negative", "Ne…
$ `histology group:ch1`                <chr> "Infiltrating Ductal Carcinoma", "Infiltrating Ductal Carcinoma…
$ `histology:ch1`                      <chr> "Infiltrating Ductal Carcinoma", "Infiltrating Ductal Carcinoma…
$ `menopausal status:ch1`              <chr> "Post-Menopausal", "Post-Menopausal", NA, "Post-Menopausal", "P…
$ `metastases:ch1`                     <chr> "No mets", "No mets", "No mets", "No mets", NA, NA, NA, NA, "No…
$ `positive nodes:ch1`                 <chr> "1 - 3", "0", "0", "0", "4-9", NA, NA, "0", "0", "4-9", "0", "1…
$ `pr:ch1`                             <chr> "Negative", "Negative", "Negative", "Negative", "Negative", "Ne…
$ `race:ch1`                           <chr> "Caucasian", "Caucasian", "Caucasian", "Caucasian", "Caucasian"…
$ `set:ch1`                            <chr> "Validation TN", "Validation TN", "Validation TN", "Validation …
$ `tissue:ch1`                         <chr> "Breast cancer", "Breast cancer", "Breast cancer", "Breast canc…
$ `tnbc subtype:ch1`                   <chr> "Mesenchymal (MES)", "Basal-Like Immune-Activated (BLIA)", "Mes…
$ `triple-negative status:ch1`         <chr> "TN", "TN", "TN", "TN", "TN", "TN", "TN", "TN", "TN", "TN", "TN…
$ `tumor grade:ch1`                    <chr> NA, "Poorly Differentiated", "Poorly Differentiated", "Poorly D…
$ `tumor size:ch1`                     <chr> "2 - 5 cm", "<=2cm", "2 - 5 cm", "<=2cm", "2 - 5 cm", "<=2cm", …
mdata <- mdata %>% 
  select(title, contains("date"), geo_accession, contains(":ch1"))

colnames(mdata)
 [1] "title"                              "submission_date"                   
 [3] "last_update_date"                   "geo_accession"                     
 [5] "age (years):ch1"                    "ajcc stage (7th edition, 2010):ch1"
 [7] "body mass index:ch1"                "er:ch1"                            
 [9] "gender:ch1"                         "her2:ch1"                          
[11] "histology group:ch1"                "histology:ch1"                     
[13] "menopausal status:ch1"              "metastases:ch1"                    
[15] "positive nodes:ch1"                 "pr:ch1"                            
[17] "race:ch1"                           "set:ch1"                           
[19] "tissue:ch1"                         "tnbc subtype:ch1"                  
[21] "triple-negative status:ch1"         "tumor grade:ch1"                   
[23] "tumor size:ch1"                    
  
cnames <- colnames(mdata)
cnames_processed <- str_split(cnames, pattern = ":") %>% 
  map_chr(~{.x[[1]]}) %>% 
  str_replace_all(" ", "_") %>% 
  str_replace_all("-", "_") %>% 
  str_remove_all("\\(|\\)|,")

cnames_processed
 [1] "title"                       "submission_date"             "last_update_date"           
 [4] "geo_accession"               "age_years"                   "ajcc_stage_7th_edition_2010"
 [7] "body_mass_index"             "er"                          "gender"                     
[10] "her2"                        "histology_group"             "histology"                  
[13] "menopausal_status"           "metastases"                  "positive_nodes"             
[16] "pr"                          "race"                        "set"                        
[19] "tissue"                      "tnbc_subtype"                "triple_negative_status"     
[22] "tumor_grade"                 "tumor_size"                 
colnames(mdata) <- cnames_processed
rm(cnames, cnames_processed)
glimpse(mdata)
Rows: 265
Columns: 23
$ title                       <chr> "S1-H10", "S1-H14", "S1-H19", "S1-H20B", "S1-H22", "S1-H27", "S1-H28", "…
$ submission_date             <chr> "Dec 17 2015", "Dec 17 2015", "Dec 17 2015", "Dec 17 2015", "Dec 17 2015…
$ last_update_date            <chr> "Dec 18 2015", "Dec 18 2015", "Dec 18 2015", "Dec 18 2015", "Dec 18 2015…
$ geo_accession               <chr> "GSM1974566", "GSM1974567", "GSM1974568", "GSM1974569", "GSM1974570", "G…
$ age_years                   <dbl> NA, 41, 55, 55, 65, 40, 66, 57, NA, 55, 44, 78, 54, 71, 37, 51, 64, NA, …
$ ajcc_stage_7th_edition_2010 <chr> "T2N1M0", "T1N0M0", "T2N0M0", "T1cN0M0", "T2N2MX", "T1NXMX", "T1cNXMX", …
$ body_mass_index             <dbl> 32, 29, NA, 31, 38, 22, 22, 38, 29, 32, 44, 28, 34, 32, 23, 23, 36, 22, …
$ er                          <chr> "Negative", "Negative", "Negative", "Negative", "Negative", "Negative", …
$ gender                      <chr> "Female", "Female", "Female", "Female", "Female", "Female", "Female", "F…
$ her2                        <chr> "Negative", "Negative", "Negative", "Negative", "Negative", "Negative", …
$ histology_group             <chr> "Infiltrating Ductal Carcinoma", "Infiltrating Ductal Carcinoma", "Infil…
$ histology                   <chr> "Infiltrating Ductal Carcinoma", "Infiltrating Ductal Carcinoma", "Infil…
$ menopausal_status           <chr> "Post-Menopausal", "Post-Menopausal", NA, "Post-Menopausal", "Post-Menop…
$ metastases                  <chr> "No mets", "No mets", "No mets", "No mets", NA, NA, NA, NA, "No mets", N…
$ positive_nodes              <chr> "1 - 3", "0", "0", "0", "4-9", NA, NA, "0", "0", "4-9", "0", "1 - 3", "1…
$ pr                          <chr> "Negative", "Negative", "Negative", "Negative", "Negative", "Negative", …
$ race                        <chr> "Caucasian", "Caucasian", "Caucasian", "Caucasian", "Caucasian", "Caucas…
$ set                         <chr> "Validation TN", "Validation TN", "Validation TN", "Validation TN", "Val…
$ tissue                      <chr> "Breast cancer", "Breast cancer", "Breast cancer", "Breast cancer", "Bre…
$ tnbc_subtype                <chr> "Mesenchymal (MES)", "Basal-Like Immune-Activated (BLIA)", "Mesenchymal …
$ triple_negative_status      <chr> "TN", "TN", "TN", "TN", "TN", "TN", "TN", "TN", "TN", "TN", "TN", "TN", …
$ tumor_grade                 <chr> NA, "Poorly Differentiated", "Poorly Differentiated", "Poorly Differenti…
$ tumor_size                  <chr> "2 - 5 cm", "<=2cm", "2 - 5 cm", "<=2cm", "2 - 5 cm", "<=2cm", "<=2cm", …
# mdata %>% 
  # distinct(triple_negative_status, tnbc_subtype)
  # distinct(submission_date, triple_negative_status)
  # distinct(tnbc_subtype, triple_negative_status)
  # distinct(histology, histology_group)
  # distinct(er, her2, pr, triple_negative_status)

Looking at the number of samples for each combination of set and each condition.

mdata %>% 
  count(triple_negative_status, set)

Reading in raw probe intensity data

Celfiles downloaded from GEO and kept the folder celfiles/

celFiles <- list.celfiles('celfiles/', full.names = TRUE, listGzipped = TRUE)
celFiles %>% head()
[1] "celfiles//GSM1974566_S1_H10.CEL.gz"  "celfiles//GSM1974567_S1_H14.CEL.gz" 
[3] "celfiles//GSM1974568_S1_H19.CEL.gz"  "celfiles//GSM1974569_S1_H20B.CEL.gz"
[5] "celfiles//GSM1974570_S1_H22.CEL.gz"  "celfiles//GSM1974571_S1_H27.CEL.gz" 
names(celFiles) <- celFiles %>% 
  basename() %>% 
  str_split("\\.") %>% 
  map_chr(~{.x[1]}) %>% 
  str_split("_") %>% 
  map_chr(~{.x[1]}) 

head(celFiles)
                           GSM1974566                            GSM1974567 
 "celfiles//GSM1974566_S1_H10.CEL.gz"  "celfiles//GSM1974567_S1_H14.CEL.gz" 
                           GSM1974568                            GSM1974569 
 "celfiles//GSM1974568_S1_H19.CEL.gz" "celfiles//GSM1974569_S1_H20B.CEL.gz" 
                           GSM1974570                            GSM1974571 
 "celfiles//GSM1974570_S1_H22.CEL.gz"  "celfiles//GSM1974571_S1_H27.CEL.gz" 
head(mdata)
mdata <- mdata[match(mdata$geo_accession, names(celFiles)), ]

Getting only the relevant variables from the metadata.

mdata_subset <- mdata %>%
  select(geo_accession, 
         title, 
         triple_negative_status, 
         tnbc_subtype,
         submission_date,
         er,
         her2,
         pr,
         race,
         set,
         gender, 
         age_years) %>% 
  mutate(across(where(is.character), .fns = factor)) %>% 
  mutate(tnbc_subtype = if_else(is.na(as.character(tnbc_subtype)), "Not Applicable", as.character(tnbc_subtype))) %>% 
  mutate(tnbc_subtype = factor(tnbc_subtype)) %>% 
  as.data.frame()


rownames(mdata_subset) <- as.character(mdata_subset$geo_accession)

head(mdata_subset)
NA
# rawData <- read.celfiles(celFiles, phenoData = AnnotatedDataFrame(mdata_subset))
# saveRDS(object = rawData, "rawData.RDS")
rawData <- readRDS("rawData.RDS")

Looking at the dimensions of the raw expression matrix.

exprs(rawData) %>% dim()
[1] 1354896     265

Using regular rma on data (without separating by class)

res_1 <- rma(rawData)
Loading required package: RSQLite
Loading required package: DBI
Background correcting
Normalizing
Calculating Expression
# saveRDS(object = res_1, "res_1.RDS")
res_1 <- readRDS("res_1.RDS")
exprs(res_1) %>% 
  dim()
[1] 54675   265
exprs(res_1)[1:5, 1:5]
          GSM1974566 GSM1974567 GSM1974568 GSM1974569 GSM1974570
1007_s_at  10.754163  11.361803   9.690693  10.157010  10.597699
1053_at     8.625663   9.011796   7.854072   7.925281   8.456781
117_at      7.333973   7.385199   7.466878   8.048563   7.581895
121_at      9.077887   8.782080   8.885189   8.775218   8.752407
1255_g_at   4.656331   4.635625   4.536114   4.626950   5.454820

Performing class-specific RMA by reading in the expression sets separately

Getting lists of the TNBC samples and the non-TNBC samples.

tnbc_samples <- mdata_subset %>% 
  filter(triple_negative_status == "TN") %>% 
  select(geo_accession) %>% 
  unlist(use.names = F) %>% 
  as.character()

head(tnbc_samples)
[1] "GSM1974566" "GSM1974567" "GSM1974568" "GSM1974569" "GSM1974570" "GSM1974571"
nontnbc_samples <- mdata_subset %>% 
  filter(triple_negative_status == "not TN") %>% 
  select(geo_accession) %>% 
  unlist(use.names = F) %>% 
  as.character()

head(nontnbc_samples)
[1] "GSM1978883" "GSM1978884" "GSM1978885" "GSM1978886" "GSM1978887" "GSM1978888"

Creating different metadata tables for TNBC and nonTNBC.

mdata_subset_tnbc <- mdata_subset[tnbc_samples, ]
dim(mdata_subset_tnbc)
[1] 198  12
mdata_subset_nontnbc <- mdata_subset[nontnbc_samples, ]
dim(mdata_subset_nontnbc)
[1] 67 12

Reading in the TNBC files.

# rawData_tnbc <- read.celfiles(filenames = celFiles[tnbc_samples], 
#                               phenoData = AnnotatedDataFrame(mdata_subset_tnbc))
# 
# rawData_tnbc
# saveRDS(rawData_tnbc, file = "rawData_tnbc.RDS")
rawData_tnbc <- readRDS(file = "rawData_tnbc.RDS")
rawData_tnbc
ExpressionFeatureSet (storageMode: lockedEnvironment)
assayData: 1354896 features, 198 samples 
  element names: exprs 
protocolData
  rowNames: GSM1974566 GSM1974567 ... GSM1974763 (198 total)
  varLabels: exprs dates
  varMetadata: labelDescription channel
phenoData
  rowNames: GSM1974566 GSM1974567 ... GSM1974763 (198 total)
  varLabels: geo_accession title ... age_years (11 total)
  varMetadata: labelDescription channel
featureData: none
experimentData: use 'experimentData(object)'
Annotation: pd.hg.u133.plus.2 
Loading required package: pd.hg.u133.plus.2
Loading required package: RSQLite
Loading required package: DBI

Reading in the nonTNBC files.

# rawData_nontnbc <- read.celfiles(filenames = celFiles[nontnbc_samples], 
#                               phenoData = AnnotatedDataFrame(mdata_subset_nontnbc))
# 
# rawData_nontnbc
# saveRDS(rawData_nontnbc, file = "rawData_nontnbc.RDS")
rawData_nontnbc <- readRDS(file = "rawData_nontnbc.RDS")
rawData_nontnbc
ExpressionFeatureSet (storageMode: lockedEnvironment)
assayData: 1354896 features, 67 samples 
  element names: exprs 
protocolData
  rowNames: GSM1978883 GSM1978884 ... GSM1978949 (67 total)
  varLabels: exprs dates
  varMetadata: labelDescription channel
phenoData
  rowNames: GSM1978883 GSM1978884 ... GSM1978949 (67 total)
  varLabels: geo_accession title ... age_years (11 total)
  varMetadata: labelDescription channel
featureData: none
experimentData: use 'experimentData(object)'
Annotation: pd.hg.u133.plus.2 

Performing RMA on TNBC data.

# res_tnbc <- rma(rawData_tnbc)
# saveRDS(res_tnbc, file = "res_tnbc.RDS")
res_tnbc <- readRDS(file = "res_tnbc.RDS")

Performing RMA on nonTNBC data.

# res_nontnbc <- rma(rawData_nontnbc)
# saveRDS(res_nontnbc, file = "res_nontnbc.RDS")
res_nontnbc <- readRDS(file = "res_nontnbc.RDS")

Combining the expression matrices of TNBC and nonTNBC data after separate RMA.

res_joint <- cbind(exprs(res_tnbc), exprs(res_nontnbc))
res_joint[1:5, 1:5]
          GSM1974566 GSM1974567 GSM1974568 GSM1974569 GSM1974570
1007_s_at  10.703231  11.345325   9.732361  10.175704  10.576463
1053_at     8.605269   9.019553   7.794234   7.945029   8.497373
117_at      7.360884   7.373951   7.481344   8.047586   7.530427
121_at      9.100729   8.776369   8.860402   8.755606   8.766560
1255_g_at   4.664492   4.628692   4.569530   4.627230   5.467292

Saving certain CSV files for everyone else to refer to

Saving the joint expression matrix from class-specific QN.

res_joint %>% 
  as_tibble(rownames = "probe_id") %>% 
  write_csv("dataframe_files/post_classQN_expression.csv")
Error: Cannot open file for writing:
* 'dataframe_files/post_classQN_expression.csv'

Saving a subset of the metadata that I think is relevant.

Saving the TNBC and nonTNBC metadata separately, just in case.

mdata_subset_nontnbc %>% 
  write_csv("dataframe_files/metadata_subset_nontnbc.csv")

Getting sample-specific boxplots

Sample specific boxplots for regular RMA QN

res_1_df_long <- res_1 %>%
  exprs() %>% 
  as_tibble(rownames = "probeID") %>% 
  pivot_longer(cols = all_of(c(tnbc_samples, nontnbc_samples)), names_to = "sample_id", 
               values_to = "intensity") %>% 
  left_join(., mdata_subset, by = c("sample_id" = "geo_accession"))
# saveRDS(object = res_1_df_long, "res_1_df_long.RDS")
res_1_df_long <- readRDS("res_1_df_long.RDS")
p2 <- res_1_df_long %>% 
  ggplot() +
  geom_boxplot(mapping = aes(x = reorder(sample_id, as.numeric(set)), y = intensity, 
                             color = set)) +
  labs(x = "samples", 
       title = str_wrap("Sample-wise log2 intensity boxplots for whole QN")) +
  scale_color_npg() +
  theme(axis.text.x = element_blank())

p2

NA
ggsave("plots/exploration_plots/GSE76275_post_regQN_boxplots.png", 
       p2, 
       units = "cm", width = 30, height = 10)
rm(res_1_df_long)

Sample specific boxplots for classQN

res_joint_df_long <- res_joint %>% 
  as_tibble(rownames = "probeID") %>% 
  pivot_longer(cols = all_of(c(tnbc_samples, nontnbc_samples)), names_to = "sample_id", 
               values_to = "intensity")
  
# saveRDS(object = res_joint_df_long, "res_joint_df_long.RDS")
res_joint_df_long <- readRDS("res_joint_df_long.RDS")
p1 <- res_joint_df_long %>% 
  left_join(., mdata_subset, by = c("sample_id" = "geo_accession")) %>% 
  mutate(sample_id = factor(sample_id)) %>% 
  ggplot() +
  geom_boxplot(mapping = aes(x = reorder(sample_id, as.numeric(set)), y = intensity, 
                             color = set)) +
  labs(x = "samples", 
       title = str_wrap("Sample-wise log2 intensity boxplots for  classQN", 60)) +
  scale_color_npg() +
  theme(axis.text.x = element_blank())

p1 

ggsave("plots/exploration_plots/GSE76275_post_classQN_boxplots.png", 
       p1, 
       units = "cm", width = 30, height = 10)

Performing PCA

Custom functions

Function to create an annotated data frame by combining PC scores as well as metadata: useful for ggplot visualization.

get_pca_annot_df <- function(pca.obj, sample_id_col, mdata_df){
  ind_scores <- pca.obj$x
  ind_scores_reordered <- ind_scores[match(rownames(ind_scores), mdata_df[[sample_id_col]]), ] %>% 
    as_tibble(rownames = sample_id_col) %>% 
    mutate(filename = factor(!!sym(sample_id_col)))
  ind_scores_annot <- left_join(ind_scores_reordered, y = mdata_df, by = sample_id_col) %>% 
  select(all_of(colnames(mdata_subset)), contains("PC"))
  return(ind_scores_annot)
}

Performing PCA on regular RMA data

pca.res_1 <- res_1 %>% 
  exprs() %>% 
  t() %>% 
  prcomp(center = TRUE, scale = TRUE)

Getting the annotated data frame for the PCA.

pca.res_1.annot_df <- get_pca_annot_df(pca.obj = pca.res_1, sample_id_col = "geo_accession", mdata_df= mdata_subset)
head(pca.res_1.annot_df)

Visualizing PCA results

Looking at the variance explained by the first 10 PCs.

fviz_eig(pca.res_1) +
  labs(x = "Principal Component", 
       title = str_wrap("Scree plot for the first 10 principal components for regular RMA-normalized data", 60)) +
  theme(title = element_text(size = 15))

Superimposing variables in data upon sample PCA scores. The PCA does not seem to separate the TNBC and nonTNBC samples that well when regular RMA is performed.

ggplot(pca.res_1.annot_df) + 
  geom_point(mapping = aes(x = PC1, y = PC2, colour = triple_negative_status)) +
    ggtitle("Samples in first two PCs, \ncoloured by triple_negative_status for whole QN") +
  guides(colour = guide_legend(override.aes = list(size= 4))) +
  theme(axis.text.x = element_text(angle = 90, size = 7),
               title = element_text(size = 10),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(colour = "black", size = 0.5))


ggsave("plots/exploration_plots/PCA_wholeQN_TNBC_status.png")
Saving 7.29 x 4.51 in image

NA

The samples do not seem to separate well by set either.

ggplot(pca.res_1.annot_df) + 
  geom_point(mapping = aes(x = PC1, y = PC2, colour = set)) +
    ggtitle("Samples in first two PCs, \ncoloured by set (discovery or validation) for whole QN") +
  guides(colour = guide_legend(override.aes = list(size= 4))) +
  theme(axis.text.x = element_text(angle = 90, size = 7),
               title = element_text(size = 10),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(colour = "black", size = 0.5))

ggsave("plots/exploration_plots/PCA_wholeQN_set.png")
Saving 7.29 x 4.51 in image

There does not seem to be too strong of a batch effect according to submission date.

ggplot(pca.res_1.annot_df) + 
  geom_point(mapping = aes(x = PC1, y = PC2, colour = submission_date)) +
    ggtitle("Samples in first two PCs, \ncoloured by submission date for whole QN") +
  guides(colour = guide_legend(override.aes = list(size= 4))) +
  theme(axis.text.x = element_text(angle = 90, size = 7),
               title = element_text(size = 10),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(colour = "black", size = 0.5))

ggsave("plots/exploration_plots/PCA_wholeQN_set.png")
Saving 7.29 x 4.51 in image

ggplot(pca.res_1.annot_df) + 
  geom_point(mapping = aes(x = PC1, y = PC2, colour = tnbc_subtype)) +
    ggtitle("Samples in first two PCs, \ncoloured by tnbc_subtype for whole QN") +
  guides(colour = guide_legend(override.aes = list(size= 4))) +
  theme(axis.text.x = element_text(angle = 90, size = 7),
               title = element_text(size = 10),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(colour = "black", size = 0.5), legend.position = "right", legend.direction = "vertical", legend.key.width = unit(x = 0.5, units = "cm")) 

Performing PCA on separately-performed RMA data

pca.res_joint <- res_joint %>% 
  t() %>% 
  prcomp(center = TRUE, scale = TRUE)
saveRDS(pca.res_joint, "pca_res_joint.RDS")
# pca.res_joint <- readRDS("pca_res_joint.RDS")

Getting the annotated data frame for the PCA.

pca.res_joint.annot_df <- get_pca_annot_df(pca.obj = pca.res_joint, sample_id_col = "geo_accession", mdata_df= mdata_subset)
head(pca.res_joint.annot_df)

Visualizing PCA results

Looking at the variance explained by the first 10 PCs.

fviz_eig(pca.res_joint) +
  labs(x = "Principal Component", 
       title = str_wrap("Scree plot for the first 10 principal components for classQN-normalized data", 60)) +
  theme(title = element_text(size = 15))

Superimposing variables in data upon sample PCA scores. The PCA does separate the TNBC and nonTNBC samples well when class-specific RMA is performed.

ggplot(pca.res_joint.annot_df) + 
  geom_point(mapping = aes(x = PC1, y = PC2, colour = triple_negative_status)) +
    ggtitle("Samples in first two PCs, \ncoloured by triple_negative_status for classQN") +
  guides(colour = guide_legend(override.aes = list(size= 4))) +
  theme(axis.text.x = element_text(angle = 90, size = 7),
               title = element_text(size = 10),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(colour = "black", size = 0.5))

NA

The validation nonTNBC samples are separated from the discovery TNBC and validation TNBC samples.

ggplot(pca.res_joint.annot_df) + 
  geom_point(mapping = aes(x = PC1, y = PC2, colour = set)) +
    ggtitle("Samples in first two PCs, \ncoloured by set (discovery or validation) for classQN") +
  guides(colour = guide_legend(override.aes = list(size= 4))) +
  theme(axis.text.x = element_text(angle = 90, size = 7),
               title = element_text(size = 10),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(colour = "black", size = 0.5))

Submission date is perfectly confounded with TNBC status. May or may not be batch effects.

ggplot(pca.res_joint.annot_df) + 
  geom_point(mapping = aes(x = PC1, y = PC2, colour = submission_date)) +
    ggtitle("Samples in first two PCs, \ncoloured by submission date) for classQN") +
  guides(colour = guide_legend(override.aes = list(size= 4))) +
  theme(axis.text.x = element_text(angle = 90, size = 7),
               title = element_text(size = 10),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(colour = "black", size = 0.5))

ggplot(pca.res_joint.annot_df) + 
  geom_point(mapping = aes(x = PC1, y = PC2, colour = tnbc_subtype)) +
    ggtitle("Samples in first two PCs, \ncoloured by tnbc_subtype for classQN") +
  guides(colour = guide_legend(override.aes = list(size= 4))) +
  theme(axis.text.x = element_text(angle = 90, size = 7),
               title = element_text(size = 10),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(colour = "black", size = 0.5), legend.position = "right", legend.direction = "vertical", legend.key.width = unit(x = 0.5, units = "cm")) 

Performing hierarchical clustering

Getting distances

perform_min_max <- function(x){
  mm_transformation <- preProcess(x, method = "range")
  rescaled <- predict(mm_transformation, x)
  return(rescaled)
}

Getting distances after performing min max normalization.

res_1_dists <- exprs(res_1) %>% 
  t() %>% 
  perform_min_max() %>% 
  dist(method = "euclidean")
  
# saveRDS(res_1_dists, "res_1_dists.RDS")
res_1_dists <- readRDS("res_1_dists.RDS")
res_joint_dists <- res_joint %>% 
    t() %>% 
  perform_min_max() %>% 
  dist(method = "euclidean")
  
# saveRDS(res_joint_dists, "res_joint_dists.RDS")
res_joint_dists <- readRDS("res_joint_dists.RDS")

Using dendrograms

res_1_dend <- res_1_dists %>% 
  hclust() %>% 
  as.dendrogram()
res_joint_dend <- res_joint_dists %>%
  hclust() %>% 
  as.dendrogram()
library(dendextend)

---------------------
Welcome to dendextend version 1.15.2
Type citation('dendextend') for how to cite the package.

Type browseVignettes(package = 'dendextend') for the package vignette.
The github page is: https://github.com/talgalili/dendextend/

Suggestions and bug-reports can be submitted at: https://github.com/talgalili/dendextend/issues
You may ask questions at stackoverflow, use the r and dendextend tags: 
     https://stackoverflow.com/questions/tagged/dendextend

    To suppress this message use:  suppressPackageStartupMessages(library(dendextend))
---------------------


Attaching package: ‘dendextend’

The following object is masked from ‘package:Biostrings’:

    nnodes

The following object is masked from ‘package:stats’:

    cutree
# res_1_dend %>% 
#   labels()
# res_1_dend %>% 
#   order.dendrogram()
# (res_1 %>% 
#   exprs() %>% 
#   colnames())[28]
  
res_1_dend_laborder <- res_1_dend %>% 
  labels()
mycolors <- ifelse(mdata_subset[res_1_dend_laborder, ]$triple_negative_status == "TN", "forestgreen", "maroon")
par(mar = c(10,2,1,1))
res_1_dend %>% 
  set("labels_cex", 0.1) %>% 
  plot()

colored_bars(colors = mycolors, dend = res_1_dend, rowLabels = "TN Status", add = TRUE)

res_joint_dend_laborder <- res_joint_dend %>% 
  labels()
mycolors <- ifelse(mdata_subset[res_joint_dend_laborder, ]$triple_negative_status == "TN", "forestgreen", "maroon")
par(mar = c(10,2,1,1))
res_joint_dend %>% 
  set("labels_cex", 0.1) %>% 
  plot()

colored_bars(colors = mycolors, dend = res_joint_dend, rowLabels = "TN Status", add = TRUE)

Using heatmaps

Function to process distance object into a distance matrix for heatmap visualization.

get_distmat <- function(x){
  distmat <- as.matrix(x)
  colnames(distmat) <- NULL
  diag(distmat) <- NA
  return(distmat)
}
row_annot <- mdata_subset %>% 
  select(set, submission_date, tnbc_subtype)

head(row_annot)
set.seed(1)
row_colours <- list( "set" = c("steelblue", "maroon", "gold"), 
                     "submission_date" = sample(colorRampPalette(colors = brewer.pal(n = 8, name = "Set2"))(8), 2),
                     "tnbc_subtype" = sample(colorRampPalette(colors = brewer.pal(n = 8, name = "Dark2"))(8), 5)
                     )

names(row_colours$set) <- as.character(unique(mdata_subset$set))
names(row_colours$submission_date) <- as.character(unique(mdata_subset$submission_date))
names(row_colours$tnbc_subtype) <- as.character(unique(mdata_subset$tnbc_subtype))
str(row_colours)
List of 3
 $ set            : Named chr [1:3] "steelblue" "maroon" "gold"
  ..- attr(*, "names")= chr [1:3] "Validation TN" "Discovery TN" "Validation not TN"
 $ submission_date: Named chr [1:2] "#66C2A5" "#E78AC3"
  ..- attr(*, "names")= chr [1:2] "Dec 17 2015" "Dec 22 2015"
 $ tnbc_subtype   : Named chr [1:5] "#A6761D" "#1B9E77" "#D95F02" "#66A61E" ...
  ..- attr(*, "names")= chr [1:5] "Mesenchymal (MES)" "Basal-Like Immune-Activated (BLIA)" "Basal-Like Immune-Suppressed (BLIS)" "Luminal-AR (LAR)" ...
res_1_dists %>% 
  get_distmat() %>% 
pheatmap(.,
         annotation_row = row_annot, 
         annotation_colors = row_colours,
         show_colnames = F,
         show_rownames = F,
         cutree_rows = 3,
         main = str_wrap("Heatmap of sample distances for whole QN expression matrix", 60),
         legend_labels = c("small distance", "large distance"),
         legend_breaks = c(min(., na.rm = TRUE), 
                         max(., na.rm = TRUE)))

res_joint_dists %>% 
  get_distmat() %>% 
pheatmap(.,
         annotation_row = row_annot, 
         annotation_colors = row_colours,
         show_colnames = F,
         show_rownames = F,
         cutree_rows = 2,
         cutree_cols = 2,
         main = str_wrap("Heatmap of sample distances for class-specific QN expression matrix", 60),
         legend_labels = c("small distance", "large distance"),
         legend_breaks = c(min(., na.rm = TRUE), 
                         max(., na.rm = TRUE)))

Performing SVA

Performing SVA on regular RMA data

full_mod <- mdata_subset %>% 
  select(geo_accession, triple_negative_status) %>% 
  arrange(triple_negative_status) %>% 
  model.matrix(~triple_negative_status, data = .)

head(full_mod)
           (Intercept) triple_negative_statusTN
GSM1978883           1                        0
GSM1978884           1                        0
GSM1978885           1                        0
GSM1978886           1                        0
GSM1978887           1                        0
GSM1978888           1                        0
red_mod <- model.matrix(~1, data = mdata_subset)

head(red_mod)
           (Intercept)
GSM1974566           1
GSM1974567           1
GSM1974568           1
GSM1974569           1
GSM1974570           1
GSM1974571           1

Get number of significant surrogate variables.

n.sv.wholeQN <- num.sv(exprs(res_1), full_mod, method="leek")
n.sv.wholeQN
[1] 0
svobj.wholeQN <- sva(exprs(res_1), mod = full_mod, mod0 = red_mod, n.sv = 1)
Number of significant surrogate variables is:  1 
Iteration (out of 5 ):1  2  3  4  5  
left_join(sv_df.wholeQN, mdata, by = "geo_accession") %>% 
  mutate(index = 5) %>% 
  ggplot() +
  # geom_col(mapping = aes(y = fct_reorder(geo_accession, sv, .fun = function(x){x}), x = sv, fill = set)) +
  geom_boxplot(mapping = aes(x = submission_date, y = sv, fill = set)) +
  theme_light() +
  labs(y = "Surrogate Variable Value", title = "Distribution of latent variable estimated by SVA for different grouping factors")

  

# ggsave("plots/exploration_plots/sva_grouping_normalRMA.png")

Performing SVA on class-specific quantile normalized data

Create full model matrix.

full_mod <- mdata_subset %>% 
  select(geo_accession, triple_negative_status) %>% 
  arrange(triple_negative_status) %>% 
  model.matrix(~triple_negative_status, data = .)

head(full_mod)
           (Intercept) triple_negative_statusTN
GSM1978883           1                        0
GSM1978884           1                        0
GSM1978885           1                        0
GSM1978886           1                        0
GSM1978887           1                        0
GSM1978888           1                        0

Create reduced model matrix.

red_mod <- model.matrix(~1, data = mdata_subset)

head(red_mod)
           (Intercept)
GSM1974566           1
GSM1974567           1
GSM1974568           1
GSM1974569           1
GSM1974570           1
GSM1974571           1

Get number of significant surrogate variables.

n.sv.classQN <- num.sv(res_joint, full_mod, method="leek")
n.sv.classQN
[1] 1

Perform SVA on classQN-normalized expression matrix.

svobj.classQN <- sva(res_joint, mod = full_mod, mod0 = red_mod, n.sv = n.sv.classQN)
Number of significant surrogate variables is:  1 
Iteration (out of 5 ):1  2  3  4  5  
sv_df.classQN <- tibble("geo_accession" = colnames(res_joint), "sv" = svobj.classQN$sv)

head(sv_df.classQN)
saveRDS(sv_df.classQN, "sv_df_classQN.RDS")
# sv_df.classQN <- readRDS("sv_df_classQN.RDS")
left_join(sv_df.classQN , mdata, by = "geo_accession") %>% 
  mutate(index = 5) %>% 
  ggplot() +
  # geom_col(mapping = aes(y = fct_reorder(geo_accession, sv, .fun = function(x){x}), x = sv, fill = set)) +
  geom_boxplot(mapping = aes(x = submission_date, y = sv, fill = set)) +
  theme_light() +
  labs(y = "Surrogate Variable Value", title = "Distribution of latent variable estimated by SVA for different grouping factors")
  

ggsave("plots/exploration_plots/sva_grouping_classQN.png")
Saving 7.29 x 4.51 in image

Trying to see if the SVA estimates a batch when QN is not applied

In this attempt, I perform no quantile normalization while performing RMA. If QN has not been performed and a surrogate variable shows up that corresponds to batch, batch effects are probably present.

rawData.summary <- rma(rawData, background = TRUE, normalize = FALSE)
Background correcting
Calculating Expression
rawData.summary_df_long <- rawData.summary %>% 
  exprs() %>% 
  as_tibble(rownames = "probeID") %>% 
  pivot_longer(cols = all_of(c(tnbc_samples, nontnbc_samples)), names_to = "sample_id", 
               values_to = "intensity") %>% 
  left_join(., mdata_subset, by = c("sample_id" = "geo_accession"))
p3 <- rawData.summary_df_long %>% 
  ggplot() +
  geom_boxplot(mapping = aes(x = reorder(sample_id, as.numeric(set)), y = intensity, 
                             color = set)) +
  labs(x = "samples", 
       title = str_wrap("Sample-wise log2 intensity boxplots in the absence of QN", 60)) +
  scale_color_npg() +
  theme(axis.text.x = element_blank())

p3

ggsave("plots/exploration_plots/GSE76275_noQN_boxplots.png", 
       p1, 
       units = "cm", width = 30, height = 10)

Getting the number of surrogate variables in the absence of quantile normalization.

n.sv.nonorm <- num.sv(exprs(rawData.summary), full_mod, method="leek")

There is one surrogate variable present in the absence of QN.

n.sv.nonorm
[1] 1
svobj.nonorm <- sva(exprs(rawData.summary), mod = full_mod, mod0 = red_mod, n.sv = n.sv.nonorm)
Number of significant surrogate variables is:  1 
Iteration (out of 5 ):1  2  3  4  5  
sv_df.nonorm <- tibble("geo_accession" = colnames(exprs(rawData.summary)), "sv" = svobj.nonorm$sv)

head(sv_df.nonorm)
left_join(sv_df.nonorm, mdata, by = "geo_accession") %>% 
  mutate(index = 5) %>% 
  ggplot() +
  # geom_col(mapping = aes(y = fct_reorder(geo_accession, sv, .fun = function(x){x}), x = sv, fill = set)) +
  geom_boxplot(mapping = aes(x = submission_date, y = sv, fill = set)) +
  theme_light() +
  labs(y = "Surrogate Variable Value", 
       title = str_wrap("Distribution of latent variable estimated by SVA for different grouping factors", 60))

ggsave("plots/exploration_plots/sva_grouping_noQN.png")
Saving 7.29 x 4.51 in image

LS0tCnRpdGxlOiAiRXhwbG9yZSBHU0U3NjI3NSIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCiMgTG9hZGluZyBsaWJyYXJpZXMKCmBgYHtyfQpsaWJyYXJ5KEdFT3F1ZXJ5KQpsaWJyYXJ5KG9saWdvKQojIGxpYnJhcnkoYWZmeSkKbGlicmFyeShsaW1tYSkKbGlicmFyeShzdmEpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdnc2NpKQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmxpYnJhcnkocGhlYXRtYXApCmxpYnJhcnkoZGVuZGV4dGVuZCkKbGlicmFyeShjYXJldCkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCiMgbGlicmFyeSh2aXJpZGlzKQpgYGAKCgojIEN1c3RvbSBmdW5jdGlvbnMKCmBgYHtyfQojIGdpdmVuIGEgbWF0cml4LCBwZXJmb3JtIG1pbi1tYXggc2NhbGluZyBvbiBpdHMgY29sdW1ucwptaW5fbWF4X21hdCA8LSBmdW5jdGlvbihtYXQpewogIG1hdF9yZXNjYWxlZCA8LSBhcHBseShtYXQsIDIsIGZ1bmN0aW9uKHYpewogICAgdl9yYW5nZSA8LSByYW5nZSh2KQogICAgbmFtZXModl9yYW5nZSkgPC0gYygibWluaW11bSIsICJtYXhpbXVtIikKICAgIHJhbmdlX2RpZmZlcmVuY2UgPC0gdl9yYW5nZVsibWF4aW11bSJdIC0gdl9yYW5nZVsibWluaW11bSJdCiAgICByZXNjYWxlZCA8LSAodiAtIHZfcmFuZ2VbIm1pbmltdW0iXSkvcmFuZ2VfZGlmZmVyZW5jZQogICAgcmV0dXJuKHJlc2NhbGVkKQogIH0pCiAgcmV0dXJuKG1hdF9yZXNjYWxlZCkKfQpgYGAKCiMgR2V0dGluZyBkYXRhIGZyb20gR0VPcXVlcnkKCmBgYHtyfQojIGdlb2RhdGEgPC0gR0VPcXVlcnk6OmdldEdFTyhHRU8gPSAiR1NFNzYyNzUiLCBkZXN0ZGlyID0gIi4vdGVtcGZpbGVzIikKIyBnZW9kYXRhIDwtIEdFT3F1ZXJ5OjpnZXRHRU8oZmlsZW5hbWUgPSAiLi90ZW1wZmlsZXMvR1NFNzYyNzVfc2VyaWVzX21hdHJpeC50eHQuZ3oiKQpgYGAKCgpgYGB7cn0KIyBzYXZlUkRTKGdlb2RhdGEsICJnZW9kYXRhLlJEUyIpCmdlb2RhdGEgPC0gcmVhZFJEUygiZ2VvZGF0YS5SRFMiKQpgYGAKCgpgYGB7cn0KIyBtZGF0YSA8LSBnZW9kYXRhICU+JSAKIyAgIHBsdWNrKDEpICU+JSAKIyAgIHBoZW5vRGF0YSgpICU+JQojICAgcERhdGEoKSAlPiUgYXNfdGliYmxlKCkKYGBgCgoKYGBge3J9CiMgZmVhdHVyZV9kYXRhIDwtIGdlb2RhdGEgJT4lIAojICAgcGx1Y2soMSkgJT4lIAojICAgZmVhdHVyZURhdGEoKQogIApgYGAKCgpgYGB7cn0KIyB3cml0ZV9jc3YobWRhdGEsICJyYXdfbWRhdGEuY3N2IikKbWRhdGEgPC0gcmVhZF9jc3YoInJhd19tZGF0YS5jc3YiKQpgYGAKCgpgYGB7cn0KIyBzYXZlUkRTKGZlYXR1cmVfZGF0YSwgImZlYXR1cmVEYXRhLlJEUyIpCiMgZmVhdHVyZV9kYXRhIDwtIHJlYWRSRFMoImZlYXR1cmVEYXRhLlJEUyIpCmBgYAoKIyBJbnNwZWN0aW5nIGFuZCBjbGVhbmluZyB0aGUgbWV0YWRhdGEKCmBgYHtyfQptZGF0YSAlPiUgCiAgZ2xpbXBzZSgpCmBgYAoKCmBgYHtyfQptZGF0YSA8LSBtZGF0YSAlPiUgCiAgc2VsZWN0KHRpdGxlLCBjb250YWlucygiZGF0ZSIpLCBnZW9fYWNjZXNzaW9uLCBjb250YWlucygiOmNoMSIpKQoKY29sbmFtZXMobWRhdGEpCiAgCmBgYAoKCmBgYHtyfQpjbmFtZXMgPC0gY29sbmFtZXMobWRhdGEpCmBgYAoKCmBgYHtyfQpjbmFtZXNfcHJvY2Vzc2VkIDwtIHN0cl9zcGxpdChjbmFtZXMsIHBhdHRlcm4gPSAiOiIpICU+JSAKICBtYXBfY2hyKH57LnhbWzFdXX0pICU+JSAKICBzdHJfcmVwbGFjZV9hbGwoIiAiLCAiXyIpICU+JSAKICBzdHJfcmVwbGFjZV9hbGwoIi0iLCAiXyIpICU+JSAKICBzdHJfcmVtb3ZlX2FsbCgiXFwofFxcKXwsIikKCmNuYW1lc19wcm9jZXNzZWQKYGBgCgoKYGBge3J9CmNvbG5hbWVzKG1kYXRhKSA8LSBjbmFtZXNfcHJvY2Vzc2VkCnJtKGNuYW1lcywgY25hbWVzX3Byb2Nlc3NlZCkKYGBgCgoKYGBge3J9CmdsaW1wc2UobWRhdGEpCmBgYAoKCmBgYHtyfQojIG1kYXRhICU+JSAKICAjIGRpc3RpbmN0KHRyaXBsZV9uZWdhdGl2ZV9zdGF0dXMsIHRuYmNfc3VidHlwZSkKICAjIGRpc3RpbmN0KHN1Ym1pc3Npb25fZGF0ZSwgdHJpcGxlX25lZ2F0aXZlX3N0YXR1cykKICAjIGRpc3RpbmN0KHRuYmNfc3VidHlwZSwgdHJpcGxlX25lZ2F0aXZlX3N0YXR1cykKICAjIGRpc3RpbmN0KGhpc3RvbG9neSwgaGlzdG9sb2d5X2dyb3VwKQogICMgZGlzdGluY3QoZXIsIGhlcjIsIHByLCB0cmlwbGVfbmVnYXRpdmVfc3RhdHVzKQpgYGAKCkxvb2tpbmcgYXQgdGhlIG51bWJlciBvZiBzYW1wbGVzIGZvciBlYWNoIGNvbWJpbmF0aW9uIG9mIHNldCBhbmQgZWFjaCBjb25kaXRpb24uCgpgYGB7cn0KbWRhdGEgJT4lIAogIGNvdW50KHRyaXBsZV9uZWdhdGl2ZV9zdGF0dXMsIHNldCkKYGBgCgoKIyBSZWFkaW5nIGluIHJhdyBwcm9iZSBpbnRlbnNpdHkgZGF0YQoKQ2VsZmlsZXMgZG93bmxvYWRlZCBmcm9tIEdFTyBhbmQga2VwdCB0aGUgZm9sZGVyIGNlbGZpbGVzLwoKYGBge3J9CmNlbEZpbGVzIDwtIGxpc3QuY2VsZmlsZXMoJ2NlbGZpbGVzLycsIGZ1bGwubmFtZXMgPSBUUlVFLCBsaXN0R3ppcHBlZCA9IFRSVUUpCmNlbEZpbGVzICU+JSBoZWFkKCkKYGBgCgoKCmBgYHtyfQpuYW1lcyhjZWxGaWxlcykgPC0gY2VsRmlsZXMgJT4lIAogIGJhc2VuYW1lKCkgJT4lIAogIHN0cl9zcGxpdCgiXFwuIikgJT4lIAogIG1hcF9jaHIofnsueFsxXX0pICU+JSAKICBzdHJfc3BsaXQoIl8iKSAlPiUgCiAgbWFwX2Nocih+ey54WzFdfSkgCgpoZWFkKGNlbEZpbGVzKQpgYGAKCgpgYGB7cn0KaGVhZChtZGF0YSkKYGBgCgpgYGB7cn0KbWRhdGEgPC0gbWRhdGFbbWF0Y2gobWRhdGEkZ2VvX2FjY2Vzc2lvbiwgbmFtZXMoY2VsRmlsZXMpKSwgXQpgYGAKCgpHZXR0aW5nIG9ubHkgdGhlIHJlbGV2YW50IHZhcmlhYmxlcyBmcm9tIHRoZSBtZXRhZGF0YS4KCgpgYGB7cn0KbWRhdGFfc3Vic2V0IDwtIG1kYXRhICU+JQogIHNlbGVjdChnZW9fYWNjZXNzaW9uLCAKICAgICAgICAgdGl0bGUsIAogICAgICAgICB0cmlwbGVfbmVnYXRpdmVfc3RhdHVzLCAKICAgICAgICAgdG5iY19zdWJ0eXBlLAogICAgICAgICBzdWJtaXNzaW9uX2RhdGUsCiAgICAgICAgIGVyLAogICAgICAgICBoZXIyLAogICAgICAgICBwciwKICAgICAgICAgcmFjZSwKICAgICAgICAgc2V0LAogICAgICAgICBnZW5kZXIsIAogICAgICAgICBhZ2VfeWVhcnMpICU+JSAKICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLmNoYXJhY3RlciksIC5mbnMgPSBmYWN0b3IpKSAlPiUgCiAgbXV0YXRlKHRuYmNfc3VidHlwZSA9IGlmX2Vsc2UoaXMubmEoYXMuY2hhcmFjdGVyKHRuYmNfc3VidHlwZSkpLCAiTm90IEFwcGxpY2FibGUiLCBhcy5jaGFyYWN0ZXIodG5iY19zdWJ0eXBlKSkpICU+JSAKICBtdXRhdGUodG5iY19zdWJ0eXBlID0gZmFjdG9yKHRuYmNfc3VidHlwZSkpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkKCgpyb3duYW1lcyhtZGF0YV9zdWJzZXQpIDwtIGFzLmNoYXJhY3RlcihtZGF0YV9zdWJzZXQkZ2VvX2FjY2Vzc2lvbikKCmhlYWQobWRhdGFfc3Vic2V0KQoKYGBgCgpgYGB7cn0KIyByYXdEYXRhIDwtIHJlYWQuY2VsZmlsZXMoY2VsRmlsZXMsIHBoZW5vRGF0YSA9IEFubm90YXRlZERhdGFGcmFtZShtZGF0YV9zdWJzZXQpKQpgYGAKCgpgYGB7cn0KIyBzYXZlUkRTKG9iamVjdCA9IHJhd0RhdGEsICJyYXdEYXRhLlJEUyIpCnJhd0RhdGEgPC0gcmVhZFJEUygicmF3RGF0YS5SRFMiKQpgYGAKCkxvb2tpbmcgYXQgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIHJhdyBleHByZXNzaW9uIG1hdHJpeC4KCmBgYHtyfQpleHBycyhyYXdEYXRhKSAlPiUgZGltKCkKYGBgCgoKIyMgVXNpbmcgcmVndWxhciBybWEgb24gZGF0YSAod2l0aG91dCBzZXBhcmF0aW5nIGJ5IGNsYXNzKQoKYGBge3J9CiMgcmVzXzEgPC0gcm1hKHJhd0RhdGEpCmBgYAoKCmBgYHtyfQojIHNhdmVSRFMob2JqZWN0ID0gcmVzXzEsICJyZXNfMS5SRFMiKQpyZXNfMSA8LSByZWFkUkRTKCJyZXNfMS5SRFMiKQpgYGAKCgpgYGB7cn0KZXhwcnMocmVzXzEpICU+JSAKICBkaW0oKQpgYGAKCgpgYGB7cn0KZXhwcnMocmVzXzEpWzE6NSwgMTo1XQpgYGAKCgojIyBQZXJmb3JtaW5nIGNsYXNzLXNwZWNpZmljIFJNQSBieSByZWFkaW5nIGluIHRoZSBleHByZXNzaW9uIHNldHMgc2VwYXJhdGVseQoKCkdldHRpbmcgbGlzdHMgb2YgdGhlIFROQkMgc2FtcGxlcyBhbmQgdGhlIG5vbi1UTkJDIHNhbXBsZXMuCgpgYGB7cn0KdG5iY19zYW1wbGVzIDwtIG1kYXRhX3N1YnNldCAlPiUgCiAgZmlsdGVyKHRyaXBsZV9uZWdhdGl2ZV9zdGF0dXMgPT0gIlROIikgJT4lIAogIHNlbGVjdChnZW9fYWNjZXNzaW9uKSAlPiUgCiAgdW5saXN0KHVzZS5uYW1lcyA9IEYpICU+JSAKICBhcy5jaGFyYWN0ZXIoKQoKaGVhZCh0bmJjX3NhbXBsZXMpCgpub250bmJjX3NhbXBsZXMgPC0gbWRhdGFfc3Vic2V0ICU+JSAKICBmaWx0ZXIodHJpcGxlX25lZ2F0aXZlX3N0YXR1cyA9PSAibm90IFROIikgJT4lIAogIHNlbGVjdChnZW9fYWNjZXNzaW9uKSAlPiUgCiAgdW5saXN0KHVzZS5uYW1lcyA9IEYpICU+JSAKICBhcy5jaGFyYWN0ZXIoKQoKaGVhZChub250bmJjX3NhbXBsZXMpCmBgYAoKCkNyZWF0aW5nIGRpZmZlcmVudCBtZXRhZGF0YSB0YWJsZXMgZm9yIFROQkMgYW5kIG5vblROQkMuCgpgYGB7cn0KbWRhdGFfc3Vic2V0X3RuYmMgPC0gbWRhdGFfc3Vic2V0W3RuYmNfc2FtcGxlcywgXQpkaW0obWRhdGFfc3Vic2V0X3RuYmMpCm1kYXRhX3N1YnNldF9ub250bmJjIDwtIG1kYXRhX3N1YnNldFtub250bmJjX3NhbXBsZXMsIF0KZGltKG1kYXRhX3N1YnNldF9ub250bmJjKQpgYGAKCgpSZWFkaW5nIGluIHRoZSBUTkJDIGZpbGVzLgoKYGBge3J9CiMgcmF3RGF0YV90bmJjIDwtIHJlYWQuY2VsZmlsZXMoZmlsZW5hbWVzID0gY2VsRmlsZXNbdG5iY19zYW1wbGVzXSwgCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGhlbm9EYXRhID0gQW5ub3RhdGVkRGF0YUZyYW1lKG1kYXRhX3N1YnNldF90bmJjKSkKIyAKIyByYXdEYXRhX3RuYmMKYGBgCgoKYGBge3J9CiMgc2F2ZVJEUyhyYXdEYXRhX3RuYmMsIGZpbGUgPSAicmF3RGF0YV90bmJjLlJEUyIpCnJhd0RhdGFfdG5iYyA8LSByZWFkUkRTKGZpbGUgPSAicmF3RGF0YV90bmJjLlJEUyIpCmBgYAoKYGBge3J9CnJhd0RhdGFfdG5iYwpgYGAKCgpSZWFkaW5nIGluIHRoZSBub25UTkJDIGZpbGVzLgoKYGBge3J9CiMgcmF3RGF0YV9ub250bmJjIDwtIHJlYWQuY2VsZmlsZXMoZmlsZW5hbWVzID0gY2VsRmlsZXNbbm9udG5iY19zYW1wbGVzXSwgCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGhlbm9EYXRhID0gQW5ub3RhdGVkRGF0YUZyYW1lKG1kYXRhX3N1YnNldF9ub250bmJjKSkKIyAKIyByYXdEYXRhX25vbnRuYmMKYGBgCgoKCmBgYHtyfQojIHNhdmVSRFMocmF3RGF0YV9ub250bmJjLCBmaWxlID0gInJhd0RhdGFfbm9udG5iYy5SRFMiKQpyYXdEYXRhX25vbnRuYmMgPC0gcmVhZFJEUyhmaWxlID0gInJhd0RhdGFfbm9udG5iYy5SRFMiKQpgYGAKCgpgYGB7cn0KcmF3RGF0YV9ub250bmJjCmBgYAoKClBlcmZvcm1pbmcgUk1BIG9uIFROQkMgZGF0YS4KCmBgYHtyfQojIHJlc190bmJjIDwtIHJtYShyYXdEYXRhX3RuYmMpCmBgYAoKCmBgYHtyfQojIHNhdmVSRFMocmVzX3RuYmMsIGZpbGUgPSAicmVzX3RuYmMuUkRTIikKcmVzX3RuYmMgPC0gcmVhZFJEUyhmaWxlID0gInJlc190bmJjLlJEUyIpCmBgYAoKClBlcmZvcm1pbmcgUk1BIG9uIG5vblROQkMgZGF0YS4KCmBgYHtyfQojIHJlc19ub250bmJjIDwtIHJtYShyYXdEYXRhX25vbnRuYmMpCmBgYAoKCmBgYHtyfQojIHNhdmVSRFMocmVzX25vbnRuYmMsIGZpbGUgPSAicmVzX25vbnRuYmMuUkRTIikKcmVzX25vbnRuYmMgPC0gcmVhZFJEUyhmaWxlID0gInJlc19ub250bmJjLlJEUyIpCmBgYAoKCkNvbWJpbmluZyB0aGUgZXhwcmVzc2lvbiBtYXRyaWNlcyBvZiBUTkJDIGFuZCBub25UTkJDIGRhdGEgYWZ0ZXIgc2VwYXJhdGUgUk1BLgoKYGBge3J9CnJlc19qb2ludCA8LSBjYmluZChleHBycyhyZXNfdG5iYyksIGV4cHJzKHJlc19ub250bmJjKSkKYGBgCgoKYGBge3J9CnJlc19qb2ludFsxOjUsIDE6NV0KYGBgCgojIFNhdmluZyBjZXJ0YWluIENTViBmaWxlcyBmb3IgZXZlcnlvbmUgZWxzZSB0byByZWZlciB0bwoKU2F2aW5nIHRoZSBqb2ludCBleHByZXNzaW9uIG1hdHJpeCBmcm9tIGNsYXNzLXNwZWNpZmljIFFOLgoKYGBge3J9CnJlc19qb2ludCAlPiUgCiAgYXNfdGliYmxlKHJvd25hbWVzID0gInByb2JlX2lkIikgJT4lIAogIHdyaXRlX2NzdigiZGF0YWZyYW1lX2ZpbGVzL3Bvc3RfY2xhc3NRTl9leHByZXNzaW9uLmNzdiIpCmBgYAoKU2F2aW5nIGEgc3Vic2V0IG9mIHRoZSBtZXRhZGF0YSB0aGF0IEkgdGhpbmsgaXMgcmVsZXZhbnQuCgpgYGB7cn0KbWRhdGFfc3Vic2V0ICU+JSAKICB3cml0ZV9jc3YoImRhdGFmcmFtZV9maWxlcy9tZXRhZGF0YV9zdWJzZXQuY3N2IikKYGBgCgoKU2F2aW5nIHRoZSBUTkJDIGFuZCBub25UTkJDIG1ldGFkYXRhIHNlcGFyYXRlbHksIGp1c3QgaW4gY2FzZS4KCmBgYHtyfQptZGF0YV9zdWJzZXRfdG5iYyAlPiUgCiAgd3JpdGVfY3N2KCJkYXRhZnJhbWVfZmlsZXMvbWV0YWRhdGFfc3Vic2V0X3RuYmMuY3N2IikKYGBgCgoKYGBge3J9Cm1kYXRhX3N1YnNldF9ub250bmJjICU+JSAKICB3cml0ZV9jc3YoImRhdGFmcmFtZV9maWxlcy9tZXRhZGF0YV9zdWJzZXRfbm9udG5iYy5jc3YiKQpgYGAKCgojIEdldHRpbmcgc2FtcGxlLXNwZWNpZmljIGJveHBsb3RzCgojIyBTYW1wbGUgc3BlY2lmaWMgYm94cGxvdHMgZm9yIHJlZ3VsYXIgUk1BIFFOCgoKYGBge3J9CnJlc18xX2RmX2xvbmcgPC0gcmVzXzEgJT4lCiAgZXhwcnMoKSAlPiUgCiAgYXNfdGliYmxlKHJvd25hbWVzID0gInByb2JlSUQiKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBhbGxfb2YoYyh0bmJjX3NhbXBsZXMsIG5vbnRuYmNfc2FtcGxlcykpLCBuYW1lc190byA9ICJzYW1wbGVfaWQiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gImludGVuc2l0eSIpICU+JSAKICBsZWZ0X2pvaW4oLiwgbWRhdGFfc3Vic2V0LCBieSA9IGMoInNhbXBsZV9pZCIgPSAiZ2VvX2FjY2Vzc2lvbiIpKQpgYGAKCgpgYGB7cn0KIyBzYXZlUkRTKG9iamVjdCA9IHJlc18xX2RmX2xvbmcsICJyZXNfMV9kZl9sb25nLlJEUyIpCnJlc18xX2RmX2xvbmcgPC0gcmVhZFJEUygicmVzXzFfZGZfbG9uZy5SRFMiKQpgYGAKCgpgYGB7cn0KcDIgPC0gcmVzXzFfZGZfbG9uZyAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSByZW9yZGVyKHNhbXBsZV9pZCwgYXMubnVtZXJpYyhzZXQpKSwgeSA9IGludGVuc2l0eSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBzZXQpKSArCiAgbGFicyh4ID0gInNhbXBsZXMiLCAKICAgICAgIHRpdGxlID0gc3RyX3dyYXAoIlNhbXBsZS13aXNlIGxvZzIgaW50ZW5zaXR5IGJveHBsb3RzIGZvciB3aG9sZSBRTiIsIDYwKSkgKwogIHNjYWxlX2NvbG9yX25wZygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkKCnAyCiAgCmBgYAoKCmBgYHtyfQpnZ3NhdmUoInBsb3RzL2V4cGxvcmF0aW9uX3Bsb3RzL0dTRTc2Mjc1X3Bvc3RfcmVnUU5fYm94cGxvdHMucG5nIiwgCiAgICAgICBwMiwgCiAgICAgICB1bml0cyA9ICJjbSIsIHdpZHRoID0gMzAsIGhlaWdodCA9IDEwKQpgYGAKCgpgYGB7cn0Kcm0ocmVzXzFfZGZfbG9uZykKYGBgCgoKIyMgU2FtcGxlIHNwZWNpZmljIGJveHBsb3RzIGZvciBjbGFzc1FOCgpgYGB7cn0KcmVzX2pvaW50X2RmX2xvbmcgPC0gcmVzX2pvaW50ICU+JSAKICBhc190aWJibGUocm93bmFtZXMgPSAicHJvYmVJRCIpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IGFsbF9vZihjKHRuYmNfc2FtcGxlcywgbm9udG5iY19zYW1wbGVzKSksIG5hbWVzX3RvID0gInNhbXBsZV9pZCIsIAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiaW50ZW5zaXR5IikKICAKYGBgCgoKYGBge3J9CiMgc2F2ZVJEUyhvYmplY3QgPSByZXNfam9pbnRfZGZfbG9uZywgInJlc19qb2ludF9kZl9sb25nLlJEUyIpCnJlc19qb2ludF9kZl9sb25nIDwtIHJlYWRSRFMoInJlc19qb2ludF9kZl9sb25nLlJEUyIpCmBgYAoKCgpgYGB7cn0KcDEgPC0gcmVzX2pvaW50X2RmX2xvbmcgJT4lIAogIGxlZnRfam9pbiguLCBtZGF0YV9zdWJzZXQsIGJ5ID0gYygic2FtcGxlX2lkIiA9ICJnZW9fYWNjZXNzaW9uIikpICU+JSAKICBtdXRhdGUoc2FtcGxlX2lkID0gZmFjdG9yKHNhbXBsZV9pZCkpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHJlb3JkZXIoc2FtcGxlX2lkLCBhcy5udW1lcmljKHNldCkpLCB5ID0gaW50ZW5zaXR5LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IHNldCkpICsKICBsYWJzKHggPSAic2FtcGxlcyIsIAogICAgICAgdGl0bGUgPSBzdHJfd3JhcCgiU2FtcGxlLXdpc2UgbG9nMiBpbnRlbnNpdHkgYm94cGxvdHMgZm9yICBjbGFzc1FOIiwgNjApKSArCiAgc2NhbGVfY29sb3JfbnBnKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQoKcDEgCmBgYAoKCmBgYHtyfQpnZ3NhdmUoInBsb3RzL2V4cGxvcmF0aW9uX3Bsb3RzL0dTRTc2Mjc1X3Bvc3RfY2xhc3NRTl9ib3hwbG90cy5wbmciLCAKICAgICAgIHAxLCAKICAgICAgIHVuaXRzID0gImNtIiwgd2lkdGggPSAzMCwgaGVpZ2h0ID0gMTApCmBgYAoKCiMgUGVyZm9ybWluZyBQQ0EKCiMjIEN1c3RvbSBmdW5jdGlvbnMKCkZ1bmN0aW9uIHRvIGNyZWF0ZSBhbiBhbm5vdGF0ZWQgZGF0YSBmcmFtZSBieSBjb21iaW5pbmcgUEMgc2NvcmVzIGFzIHdlbGwgYXMgbWV0YWRhdGE6IHVzZWZ1bCBmb3IgZ2dwbG90IHZpc3VhbGl6YXRpb24uCgpgYGB7cn0KZ2V0X3BjYV9hbm5vdF9kZiA8LSBmdW5jdGlvbihwY2Eub2JqLCBzYW1wbGVfaWRfY29sLCBtZGF0YV9kZil7CiAgaW5kX3Njb3JlcyA8LSBwY2Eub2JqJHgKICBpbmRfc2NvcmVzX3Jlb3JkZXJlZCA8LSBpbmRfc2NvcmVzW21hdGNoKHJvd25hbWVzKGluZF9zY29yZXMpLCBtZGF0YV9kZltbc2FtcGxlX2lkX2NvbF1dKSwgXSAlPiUgCiAgICBhc190aWJibGUocm93bmFtZXMgPSBzYW1wbGVfaWRfY29sKSAlPiUgCiAgICBtdXRhdGUoZmlsZW5hbWUgPSBmYWN0b3IoISFzeW0oc2FtcGxlX2lkX2NvbCkpKQogIGluZF9zY29yZXNfYW5ub3QgPC0gbGVmdF9qb2luKGluZF9zY29yZXNfcmVvcmRlcmVkLCB5ID0gbWRhdGFfZGYsIGJ5ID0gc2FtcGxlX2lkX2NvbCkgJT4lIAogIHNlbGVjdChhbGxfb2YoY29sbmFtZXMobWRhdGFfc3Vic2V0KSksIGNvbnRhaW5zKCJQQyIpKQogIHJldHVybihpbmRfc2NvcmVzX2Fubm90KQp9CmBgYAoKCiMjIFBlcmZvcm1pbmcgUENBIG9uIHJlZ3VsYXIgUk1BIGRhdGEKCmBgYHtyfQpwY2EucmVzXzEgPC0gcmVzXzEgJT4lIAogIGV4cHJzKCkgJT4lIAogIHQoKSAlPiUgCiAgcHJjb21wKGNlbnRlciA9IFRSVUUsIHNjYWxlID0gVFJVRSkKYGBgCgoKYGBge3J9CiMgc2F2ZVJEUyhwY2EucmVzXzEsICJwY2FfcmVzMS5SRFMiKQpwY2EucmVzXzEgPC0gcmVhZFJEUygicGNhX3JlczEuUkRTIikKYGBgCgoKR2V0dGluZyB0aGUgYW5ub3RhdGVkIGRhdGEgZnJhbWUgZm9yIHRoZSBQQ0EuCgpgYGB7cn0KcGNhLnJlc18xLmFubm90X2RmIDwtIGdldF9wY2FfYW5ub3RfZGYocGNhLm9iaiA9IHBjYS5yZXNfMSwgc2FtcGxlX2lkX2NvbCA9ICJnZW9fYWNjZXNzaW9uIiwgbWRhdGFfZGY9IG1kYXRhX3N1YnNldCkKaGVhZChwY2EucmVzXzEuYW5ub3RfZGYpCmBgYAoKIyMjIFZpc3VhbGl6aW5nIFBDQSByZXN1bHRzCgpMb29raW5nIGF0IHRoZSB2YXJpYW5jZSBleHBsYWluZWQgYnkgdGhlIGZpcnN0IDEwIFBDcy4KCmBgYHtyfQpmdml6X2VpZyhwY2EucmVzXzEpICsKICBsYWJzKHggPSAiUHJpbmNpcGFsIENvbXBvbmVudCIsIAogICAgICAgdGl0bGUgPSBzdHJfd3JhcCgiU2NyZWUgcGxvdCBmb3IgdGhlIGZpcnN0IDEwIHByaW5jaXBhbCBjb21wb25lbnRzIGZvciByZWd1bGFyIFJNQS1ub3JtYWxpemVkIGRhdGEiLCA2MCkpICsKICB0aGVtZSh0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUpKQpgYGAKClN1cGVyaW1wb3NpbmcgdmFyaWFibGVzIGluIGRhdGEgdXBvbiBzYW1wbGUgUENBIHNjb3Jlcy4KVGhlIFBDQSBkb2VzIG5vdCBzZWVtIHRvIHNlcGFyYXRlIHRoZSBUTkJDIGFuZCBub25UTkJDIHNhbXBsZXMgdGhhdCB3ZWxsIHdoZW4gcmVndWxhciBSTUEgaXMgcGVyZm9ybWVkLgoKYGBge3J9CmdncGxvdChwY2EucmVzXzEuYW5ub3RfZGYpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG91ciA9IHRyaXBsZV9uZWdhdGl2ZV9zdGF0dXMpKSArCiAgICBnZ3RpdGxlKCJTYW1wbGVzIGluIGZpcnN0IHR3byBQQ3MsIFxuY29sb3VyZWQgYnkgdHJpcGxlX25lZ2F0aXZlX3N0YXR1cyBmb3Igd2hvbGUgUU4iKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9IDQpKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA3KSwKICAgICAgICAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDAuNSkpCgoKZ2dzYXZlKCJwbG90cy9leHBsb3JhdGlvbl9wbG90cy9QQ0Ffd2hvbGVRTl9UTkJDX3N0YXR1cy5wbmciKQogIApgYGAKCgoKVGhlIHNhbXBsZXMgZG8gbm90IHNlZW0gdG8gc2VwYXJhdGUgd2VsbCBieSBzZXQgZWl0aGVyLgoKYGBge3J9CmdncGxvdChwY2EucmVzXzEuYW5ub3RfZGYpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG91ciA9IHNldCkpICsKICAgIGdndGl0bGUoIlNhbXBsZXMgaW4gZmlyc3QgdHdvIFBDcywgXG5jb2xvdXJlZCBieSBzZXQgKGRpc2NvdmVyeSBvciB2YWxpZGF0aW9uKSBmb3Igd2hvbGUgUU4iKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9IDQpKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA3KSwKICAgICAgICAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDAuNSkpCgpnZ3NhdmUoInBsb3RzL2V4cGxvcmF0aW9uX3Bsb3RzL1BDQV93aG9sZVFOX3NldC5wbmciKQpgYGAKClRoZXJlIGRvZXMgbm90IHNlZW0gdG8gYmUgdG9vIHN0cm9uZyBvZiBhIGJhdGNoIGVmZmVjdCBhY2NvcmRpbmcgdG8gc3VibWlzc2lvbiBkYXRlLgoKYGBge3J9CmdncGxvdChwY2EucmVzXzEuYW5ub3RfZGYpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG91ciA9IHN1Ym1pc3Npb25fZGF0ZSkpICsKICAgIGdndGl0bGUoIlNhbXBsZXMgaW4gZmlyc3QgdHdvIFBDcywgXG5jb2xvdXJlZCBieSBzdWJtaXNzaW9uIGRhdGUgZm9yIHdob2xlIFFOIikgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPSA0KSkpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gNyksCiAgICAgICAgICAgICAgIHRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAwLjUpKQoKZ2dzYXZlKCJwbG90cy9leHBsb3JhdGlvbl9wbG90cy9QQ0Ffd2hvbGVRTl9zZXQucG5nIikKYGBgCgoKYGBge3J9CmdncGxvdChwY2EucmVzXzEuYW5ub3RfZGYpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG91ciA9IHRuYmNfc3VidHlwZSkpICsKICAgIGdndGl0bGUoIlNhbXBsZXMgaW4gZmlyc3QgdHdvIFBDcywgXG5jb2xvdXJlZCBieSB0bmJjX3N1YnR5cGUgZm9yIHdob2xlIFFOIikgKwogIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPSA0KSkpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gNyksCiAgICAgICAgICAgICAgIHRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAwLjUpLCBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLCBsZWdlbmQuZGlyZWN0aW9uID0gInZlcnRpY2FsIiwgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoeCA9IDAuNSwgdW5pdHMgPSAiY20iKSkgCmBgYAoKIyMgUGVyZm9ybWluZyBQQ0Egb24gc2VwYXJhdGVseS1wZXJmb3JtZWQgUk1BIGRhdGEKCgpgYGB7cn0KcGNhLnJlc19qb2ludCA8LSByZXNfam9pbnQgJT4lIAogIHQoKSAlPiUgCiAgcHJjb21wKGNlbnRlciA9IFRSVUUsIHNjYWxlID0gVFJVRSkKYGBgCgoKYGBge3J9CiMgc2F2ZVJEUyhwY2EucmVzX2pvaW50LCAicGNhX3Jlc19qb2ludC5SRFMiKQpwY2EucmVzX2pvaW50IDwtIHJlYWRSRFMoInBjYV9yZXNfam9pbnQuUkRTIikKYGBgCgoKR2V0dGluZyB0aGUgYW5ub3RhdGVkIGRhdGEgZnJhbWUgZm9yIHRoZSBQQ0EuCgpgYGB7cn0KcGNhLnJlc19qb2ludC5hbm5vdF9kZiA8LSBnZXRfcGNhX2Fubm90X2RmKHBjYS5vYmogPSBwY2EucmVzX2pvaW50LCBzYW1wbGVfaWRfY29sID0gImdlb19hY2Nlc3Npb24iLCBtZGF0YV9kZj0gbWRhdGFfc3Vic2V0KQpoZWFkKHBjYS5yZXNfam9pbnQuYW5ub3RfZGYpCmBgYAoKIyMjIFZpc3VhbGl6aW5nIFBDQSByZXN1bHRzCgpMb29raW5nIGF0IHRoZSB2YXJpYW5jZSBleHBsYWluZWQgYnkgdGhlIGZpcnN0IDEwIFBDcy4KCmBgYHtyfQpmdml6X2VpZyhwY2EucmVzX2pvaW50KSArCiAgbGFicyh4ID0gIlByaW5jaXBhbCBDb21wb25lbnQiLCAKICAgICAgIHRpdGxlID0gc3RyX3dyYXAoIlNjcmVlIHBsb3QgZm9yIHRoZSBmaXJzdCAxMCBwcmluY2lwYWwgY29tcG9uZW50cyBmb3IgY2xhc3NRTi1ub3JtYWxpemVkIGRhdGEiLCA2MCkpICsKICB0aGVtZSh0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUpKQpgYGAKClN1cGVyaW1wb3NpbmcgdmFyaWFibGVzIGluIGRhdGEgdXBvbiBzYW1wbGUgUENBIHNjb3Jlcy4KVGhlIFBDQSAqKmRvZXMqKiBzZXBhcmF0ZSB0aGUgVE5CQyBhbmQgbm9uVE5CQyBzYW1wbGVzIHdlbGwgd2hlbiBjbGFzcy1zcGVjaWZpYyBSTUEgaXMgcGVyZm9ybWVkLgoKYGBge3J9CmdncGxvdChwY2EucmVzX2pvaW50LmFubm90X2RmKSArIAogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gUEMxLCB5ID0gUEMyLCBjb2xvdXIgPSB0cmlwbGVfbmVnYXRpdmVfc3RhdHVzKSkgKwogICAgZ2d0aXRsZSgiU2FtcGxlcyBpbiBmaXJzdCB0d28gUENzLCBcbmNvbG91cmVkIGJ5IHRyaXBsZV9uZWdhdGl2ZV9zdGF0dXMgZm9yIGNsYXNzUU4iKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9IDQpKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA3KSwKICAgICAgICAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDAuNSkpCiAgCmBgYApUaGUgdmFsaWRhdGlvbiBub25UTkJDIHNhbXBsZXMgYXJlIHNlcGFyYXRlZCBmcm9tIHRoZSBkaXNjb3ZlcnkgVE5CQyBhbmQgdmFsaWRhdGlvbiBUTkJDIHNhbXBsZXMuCgpgYGB7cn0KZ2dwbG90KHBjYS5yZXNfam9pbnQuYW5ub3RfZGYpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG91ciA9IHNldCkpICsKICAgIGdndGl0bGUoIlNhbXBsZXMgaW4gZmlyc3QgdHdvIFBDcywgXG5jb2xvdXJlZCBieSBzZXQgKGRpc2NvdmVyeSBvciB2YWxpZGF0aW9uKSBmb3IgY2xhc3NRTiIpICsKICBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0gNCkpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDcpLAogICAgICAgICAgICAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAiYmxhY2siLCBzaXplID0gMC41KSkKYGBgCgpTdWJtaXNzaW9uIGRhdGUgaXMgcGVyZmVjdGx5IGNvbmZvdW5kZWQgd2l0aCBUTkJDIHN0YXR1cy4gTWF5IG9yIG1heSBub3QgYmUgYmF0Y2ggZWZmZWN0cy4KCmBgYHtyfQpnZ3Bsb3QocGNhLnJlc19qb2ludC5hbm5vdF9kZikgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IFBDMSwgeSA9IFBDMiwgY29sb3VyID0gc3VibWlzc2lvbl9kYXRlKSkgKwogICAgZ2d0aXRsZSgiU2FtcGxlcyBpbiBmaXJzdCB0d28gUENzLCBcbmNvbG91cmVkIGJ5IHN1Ym1pc3Npb24gZGF0ZSkgZm9yIGNsYXNzUU4iKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9IDQpKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA3KSwKICAgICAgICAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDAuNSkpCmBgYAoKCgpgYGB7cn0KZ2dwbG90KHBjYS5yZXNfam9pbnQuYW5ub3RfZGYpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBQQzEsIHkgPSBQQzIsIGNvbG91ciA9IHRuYmNfc3VidHlwZSkpICsKICAgIGdndGl0bGUoIlNhbXBsZXMgaW4gZmlyc3QgdHdvIFBDcywgXG5jb2xvdXJlZCBieSB0bmJjX3N1YnR5cGUgZm9yIGNsYXNzUU4iKSArCiAgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9IDQpKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA3KSwKICAgICAgICAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiwgc2l6ZSA9IDAuNSksIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsIGxlZ2VuZC5kaXJlY3Rpb24gPSAidmVydGljYWwiLCBsZWdlbmQua2V5LndpZHRoID0gdW5pdCh4ID0gMC41LCB1bml0cyA9ICJjbSIpKSAKYGBgCgoKCiMgUGVyZm9ybWluZyBoaWVyYXJjaGljYWwgY2x1c3RlcmluZwoKIyMgR2V0dGluZyBkaXN0YW5jZXMKCmBgYHtyfQpwZXJmb3JtX21pbl9tYXggPC0gZnVuY3Rpb24oeCl7CiAgbW1fdHJhbnNmb3JtYXRpb24gPC0gcHJlUHJvY2Vzcyh4LCBtZXRob2QgPSAicmFuZ2UiKQogIHJlc2NhbGVkIDwtIHByZWRpY3QobW1fdHJhbnNmb3JtYXRpb24sIHgpCiAgcmV0dXJuKHJlc2NhbGVkKQp9CmBgYAoKCkdldHRpbmcgZGlzdGFuY2VzIGFmdGVyIHBlcmZvcm1pbmcgbWluIG1heCBub3JtYWxpemF0aW9uLgoKCmBgYHtyfQojIHJlc18xX2Rpc3RzIDwtIGV4cHJzKHJlc18xKSAlPiUgCiMgICB0KCkgJT4lIAojICAgcGVyZm9ybV9taW5fbWF4KCkgJT4lIAojICAgZGlzdChtZXRob2QgPSAiZXVjbGlkZWFuIikKICAKYGBgCgoKYGBge3J9CiMgc2F2ZVJEUyhyZXNfMV9kaXN0cywgInJlc18xX2Rpc3RzLlJEUyIpCnJlc18xX2Rpc3RzIDwtIHJlYWRSRFMoInJlc18xX2Rpc3RzLlJEUyIpCmBgYAoKCgpgYGB7cn0KIyByZXNfam9pbnRfZGlzdHMgPC0gcmVzX2pvaW50ICU+JSAKIyAgICAgdCgpICU+JSAKIyAgIHBlcmZvcm1fbWluX21heCgpICU+JSAKIyAgIGRpc3QobWV0aG9kID0gImV1Y2xpZGVhbiIpCiAgCmBgYAoKCmBgYHtyfQojIHNhdmVSRFMocmVzX2pvaW50X2Rpc3RzLCAicmVzX2pvaW50X2Rpc3RzLlJEUyIpCnJlc19qb2ludF9kaXN0cyA8LSByZWFkUkRTKCJyZXNfam9pbnRfZGlzdHMuUkRTIikKYGBgCgoKCiMjIFVzaW5nIGRlbmRyb2dyYW1zCgpgYGB7cn0KcmVzXzFfZGVuZCA8LSByZXNfMV9kaXN0cyAlPiUgCiAgaGNsdXN0KCkgJT4lIAogIGFzLmRlbmRyb2dyYW0oKQpgYGAKCgpgYGB7cn0KcmVzX2pvaW50X2RlbmQgPC0gcmVzX2pvaW50X2Rpc3RzICU+JQogIGhjbHVzdCgpICU+JSAKICBhcy5kZW5kcm9ncmFtKCkKYGBgCgogCmBgYHtyfQpsaWJyYXJ5KGRlbmRleHRlbmQpCmBgYAoKCmBgYHtyfQojIHJlc18xX2RlbmQgJT4lIAojICAgbGFiZWxzKCkKYGBgCgoKYGBge3J9CiMgcmVzXzFfZGVuZCAlPiUgCiMgICBvcmRlci5kZW5kcm9ncmFtKCkKYGBgCgpgYGB7cn0KIyAocmVzXzEgJT4lIAojICAgZXhwcnMoKSAlPiUgCiMgICBjb2xuYW1lcygpKVsyOF0KICAKYGBgCgoKYGBge3J9CnJlc18xX2RlbmRfbGFib3JkZXIgPC0gcmVzXzFfZGVuZCAlPiUgCiAgbGFiZWxzKCkKCmBgYAoKCmBgYHtyfQpteWNvbG9ycyA8LSBpZmVsc2UobWRhdGFfc3Vic2V0W3Jlc18xX2RlbmRfbGFib3JkZXIsIF0kdHJpcGxlX25lZ2F0aXZlX3N0YXR1cyA9PSAiVE4iLCAiZm9yZXN0Z3JlZW4iLCAibWFyb29uIikKYGBgCgoKCmBgYHtyfQpwYXIobWFyID0gYygxMCwyLDEsMSkpCnJlc18xX2RlbmQgJT4lIAogIHNldCgibGFiZWxzX2NleCIsIDAuMSkgJT4lIAogIHBsb3QoKQoKY29sb3JlZF9iYXJzKGNvbG9ycyA9IG15Y29sb3JzLCBkZW5kID0gcmVzXzFfZGVuZCwgcm93TGFiZWxzID0gIlROIFN0YXR1cyIsIGFkZCA9IFRSVUUpCmBgYAoKCmBgYHtyfQpyZXNfam9pbnRfZGVuZF9sYWJvcmRlciA8LSByZXNfam9pbnRfZGVuZCAlPiUgCiAgbGFiZWxzKCkKYGBgCgoKYGBge3J9Cm15Y29sb3JzIDwtIGlmZWxzZShtZGF0YV9zdWJzZXRbcmVzX2pvaW50X2RlbmRfbGFib3JkZXIsIF0kdHJpcGxlX25lZ2F0aXZlX3N0YXR1cyA9PSAiVE4iLCAiZm9yZXN0Z3JlZW4iLCAibWFyb29uIikKYGBgCgoKCmBgYHtyfQpwYXIobWFyID0gYygxMCwyLDEsMSkpCnJlc19qb2ludF9kZW5kICU+JSAKICBzZXQoImxhYmVsc19jZXgiLCAwLjEpICU+JSAKICBwbG90KCkKCmNvbG9yZWRfYmFycyhjb2xvcnMgPSBteWNvbG9ycywgZGVuZCA9IHJlc19qb2ludF9kZW5kLCByb3dMYWJlbHMgPSAiVE4gU3RhdHVzIiwgYWRkID0gVFJVRSkKYGBgCgoKIyMgVXNpbmcgaGVhdG1hcHMKCkZ1bmN0aW9uIHRvIHByb2Nlc3MgZGlzdGFuY2Ugb2JqZWN0IGludG8gYSBkaXN0YW5jZSBtYXRyaXggZm9yIGhlYXRtYXAgdmlzdWFsaXphdGlvbi4KCmBgYHtyfQpnZXRfZGlzdG1hdCA8LSBmdW5jdGlvbih4KXsKICBkaXN0bWF0IDwtIGFzLm1hdHJpeCh4KQogIGNvbG5hbWVzKGRpc3RtYXQpIDwtIE5VTEwKICBkaWFnKGRpc3RtYXQpIDwtIE5BCiAgcmV0dXJuKGRpc3RtYXQpCn0KYGBgCgoKCmBgYHtyfQpyb3dfYW5ub3QgPC0gbWRhdGFfc3Vic2V0ICU+JSAKICBzZWxlY3Qoc2V0LCBzdWJtaXNzaW9uX2RhdGUsIHRuYmNfc3VidHlwZSkKCmhlYWQocm93X2Fubm90KQpgYGAKCgpgYGB7cn0Kc2V0LnNlZWQoMSkKcm93X2NvbG91cnMgPC0gbGlzdCggInNldCIgPSBjKCJzdGVlbGJsdWUiLCAibWFyb29uIiwgImdvbGQiKSwgCiAgICAgICAgICAgICAgICAgICAgICJzdWJtaXNzaW9uX2RhdGUiID0gc2FtcGxlKGNvbG9yUmFtcFBhbGV0dGUoY29sb3JzID0gYnJld2VyLnBhbChuID0gOCwgbmFtZSA9ICJTZXQyIikpKDgpLCAyKSwKICAgICAgICAgICAgICAgICAgICAgInRuYmNfc3VidHlwZSIgPSBzYW1wbGUoY29sb3JSYW1wUGFsZXR0ZShjb2xvcnMgPSBicmV3ZXIucGFsKG4gPSA4LCBuYW1lID0gIkRhcmsyIikpKDgpLCA1KQogICAgICAgICAgICAgICAgICAgICApCgpuYW1lcyhyb3dfY29sb3VycyRzZXQpIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUobWRhdGFfc3Vic2V0JHNldCkpCm5hbWVzKHJvd19jb2xvdXJzJHN1Ym1pc3Npb25fZGF0ZSkgPC0gYXMuY2hhcmFjdGVyKHVuaXF1ZShtZGF0YV9zdWJzZXQkc3VibWlzc2lvbl9kYXRlKSkKbmFtZXMocm93X2NvbG91cnMkdG5iY19zdWJ0eXBlKSA8LSBhcy5jaGFyYWN0ZXIodW5pcXVlKG1kYXRhX3N1YnNldCR0bmJjX3N1YnR5cGUpKQpzdHIocm93X2NvbG91cnMpCmBgYAoKCmBgYHtyfQpyZXNfMV9kaXN0cyAlPiUgCiAgZ2V0X2Rpc3RtYXQoKSAlPiUgCnBoZWF0bWFwKC4sCiAgICAgICAgIGFubm90YXRpb25fcm93ID0gcm93X2Fubm90LCAKICAgICAgICAgYW5ub3RhdGlvbl9jb2xvcnMgPSByb3dfY29sb3VycywKICAgICAgICAgc2hvd19jb2xuYW1lcyA9IEYsCiAgICAgICAgIHNob3dfcm93bmFtZXMgPSBGLAogICAgICAgICBjdXRyZWVfcm93cyA9IDMsCiAgICAgICAgIG1haW4gPSBzdHJfd3JhcCgiSGVhdG1hcCBvZiBzYW1wbGUgZGlzdGFuY2VzIGZvciB3aG9sZSBRTiBleHByZXNzaW9uIG1hdHJpeCIsIDYwKSwKICAgICAgICAgbGVnZW5kX2xhYmVscyA9IGMoInNtYWxsIGRpc3RhbmNlIiwgImxhcmdlIGRpc3RhbmNlIiksCiAgICAgICAgIGxlZ2VuZF9icmVha3MgPSBjKG1pbiguLCBuYS5ybSA9IFRSVUUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG1heCguLCBuYS5ybSA9IFRSVUUpKSkKYGBgCgoKYGBge3J9CnJlc19qb2ludF9kaXN0cyAlPiUgCiAgZ2V0X2Rpc3RtYXQoKSAlPiUgCnBoZWF0bWFwKC4sCiAgICAgICAgIGFubm90YXRpb25fcm93ID0gcm93X2Fubm90LCAKICAgICAgICAgYW5ub3RhdGlvbl9jb2xvcnMgPSByb3dfY29sb3VycywKICAgICAgICAgc2hvd19jb2xuYW1lcyA9IEYsCiAgICAgICAgIHNob3dfcm93bmFtZXMgPSBGLAogICAgICAgICBjdXRyZWVfcm93cyA9IDIsCiAgICAgICAgIGN1dHJlZV9jb2xzID0gMiwKICAgICAgICAgbWFpbiA9IHN0cl93cmFwKCJIZWF0bWFwIG9mIHNhbXBsZSBkaXN0YW5jZXMgZm9yIGNsYXNzLXNwZWNpZmljIFFOIGV4cHJlc3Npb24gbWF0cml4IiwgNjApLAogICAgICAgICBsZWdlbmRfbGFiZWxzID0gYygic21hbGwgZGlzdGFuY2UiLCAibGFyZ2UgZGlzdGFuY2UiKSwKICAgICAgICAgbGVnZW5kX2JyZWFrcyA9IGMobWluKC4sIG5hLnJtID0gVFJVRSksIAogICAgICAgICAgICAgICAgICAgICAgICAgbWF4KC4sIG5hLnJtID0gVFJVRSkpKQpgYGAKCgojIFBlcmZvcm1pbmcgU1ZBCgojIyBQZXJmb3JtaW5nIFNWQSBvbiByZWd1bGFyIFJNQSBkYXRhCgoKYGBge3J9CmZ1bGxfbW9kIDwtIG1kYXRhX3N1YnNldCAlPiUgCiAgc2VsZWN0KGdlb19hY2Nlc3Npb24sIHRyaXBsZV9uZWdhdGl2ZV9zdGF0dXMpICU+JSAKICBhcnJhbmdlKHRyaXBsZV9uZWdhdGl2ZV9zdGF0dXMpICU+JSAKICBtb2RlbC5tYXRyaXgofnRyaXBsZV9uZWdhdGl2ZV9zdGF0dXMsIGRhdGEgPSAuKQoKaGVhZChmdWxsX21vZCkKYGBgCgoKYGBge3J9CnJlZF9tb2QgPC0gbW9kZWwubWF0cml4KH4xLCBkYXRhID0gbWRhdGFfc3Vic2V0KQoKaGVhZChyZWRfbW9kKQpgYGAKCkdldCBudW1iZXIgb2Ygc2lnbmlmaWNhbnQgc3Vycm9nYXRlIHZhcmlhYmxlcy4KCmBgYHtyfQpuLnN2Lndob2xlUU4gPC0gbnVtLnN2KGV4cHJzKHJlc18xKSwgZnVsbF9tb2QsIG1ldGhvZD0ibGVlayIpCmBgYAoKCmBgYHtyfQpuLnN2Lndob2xlUU4KYGBgCgoKYGBge3J9CnN2b2JqLndob2xlUU4gPC0gc3ZhKGV4cHJzKHJlc18xKSwgbW9kID0gZnVsbF9tb2QsIG1vZDAgPSByZWRfbW9kLCBuLnN2ID0gMSkKYGBgCgoKYGBge3J9CnN2X2RmLndob2xlUU4gPC0gdGliYmxlKCJnZW9fYWNjZXNzaW9uIiA9IGNvbG5hbWVzKGV4cHJzKHJlc18xKSksICJzdiIgPSBzdm9iai53aG9sZVFOJHN2KQoKaGVhZChzdl9kZi53aG9sZVFOKQpgYGAKCgoKYGBge3J9CmxlZnRfam9pbihzdl9kZi53aG9sZVFOLCBtZGF0YSwgYnkgPSAiZ2VvX2FjY2Vzc2lvbiIpICU+JSAKICBtdXRhdGUoaW5kZXggPSA1KSAlPiUgCiAgZ2dwbG90KCkgKwogICMgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh5ID0gZmN0X3Jlb3JkZXIoZ2VvX2FjY2Vzc2lvbiwgc3YsIC5mdW4gPSBmdW5jdGlvbih4KXt4fSksIHggPSBzdiwgZmlsbCA9IHNldCkpICsKICBnZW9tX2JveHBsb3QobWFwcGluZyA9IGFlcyh4ID0gc3VibWlzc2lvbl9kYXRlLCB5ID0gc3YsIGZpbGwgPSBzZXQpKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgbGFicyh5ID0gIlN1cnJvZ2F0ZSBWYXJpYWJsZSBWYWx1ZSIsIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBsYXRlbnQgdmFyaWFibGUgZXN0aW1hdGVkIGJ5IFNWQSBmb3IgZGlmZmVyZW50IGdyb3VwaW5nIGZhY3RvcnMiKQogIAoKIyBnZ3NhdmUoInBsb3RzL2V4cGxvcmF0aW9uX3Bsb3RzL3N2YV9ncm91cGluZ19ub3JtYWxSTUEucG5nIikKYGBgCgoKIyMgUGVyZm9ybWluZyBTVkEgb24gY2xhc3Mtc3BlY2lmaWMgcXVhbnRpbGUgbm9ybWFsaXplZCBkYXRhCgpDcmVhdGUgZnVsbCBtb2RlbCBtYXRyaXguCgpgYGB7cn0KZnVsbF9tb2QgPC0gbWRhdGFfc3Vic2V0ICU+JSAKICBzZWxlY3QoZ2VvX2FjY2Vzc2lvbiwgdHJpcGxlX25lZ2F0aXZlX3N0YXR1cykgJT4lIAogIGFycmFuZ2UodHJpcGxlX25lZ2F0aXZlX3N0YXR1cykgJT4lIAogIG1vZGVsLm1hdHJpeCh+dHJpcGxlX25lZ2F0aXZlX3N0YXR1cywgZGF0YSA9IC4pCgpoZWFkKGZ1bGxfbW9kKQpgYGAKCkNyZWF0ZSByZWR1Y2VkIG1vZGVsIG1hdHJpeC4KCmBgYHtyfQpyZWRfbW9kIDwtIG1vZGVsLm1hdHJpeCh+MSwgZGF0YSA9IG1kYXRhX3N1YnNldCkKCmhlYWQocmVkX21vZCkKYGBgCgpHZXQgbnVtYmVyIG9mIHNpZ25pZmljYW50IHN1cnJvZ2F0ZSB2YXJpYWJsZXMuCgpgYGB7cn0Kbi5zdi5jbGFzc1FOIDwtIG51bS5zdihyZXNfam9pbnQsIGZ1bGxfbW9kLCBtZXRob2Q9ImxlZWsiKQpgYGAKCgpgYGB7cn0Kbi5zdi5jbGFzc1FOCmBgYAoKClBlcmZvcm0gU1ZBIG9uIGNsYXNzUU4tbm9ybWFsaXplZCBleHByZXNzaW9uIG1hdHJpeC4KCgpgYGB7cn0Kc3ZvYmouY2xhc3NRTiA8LSBzdmEocmVzX2pvaW50LCBtb2QgPSBmdWxsX21vZCwgbW9kMCA9IHJlZF9tb2QsIG4uc3YgPSBuLnN2LmNsYXNzUU4pCmBgYAoKCmBgYHtyfQpzdl9kZi5jbGFzc1FOIDwtIHRpYmJsZSgiZ2VvX2FjY2Vzc2lvbiIgPSBjb2xuYW1lcyhyZXNfam9pbnQpLCAic3YiID0gc3ZvYmouY2xhc3NRTiRzdikKCmhlYWQoc3ZfZGYuY2xhc3NRTikKYGBgCgpgYGB7cn0Kc2F2ZVJEUyhzdl9kZi5jbGFzc1FOLCAic3ZfZGZfY2xhc3NRTi5SRFMiKQojIHN2X2RmLmNsYXNzUU4gPC0gcmVhZFJEUygic3ZfZGZfY2xhc3NRTi5SRFMiKQpgYGAKCgpgYGB7cn0KbGVmdF9qb2luKHN2X2RmLmNsYXNzUU4gLCBtZGF0YSwgYnkgPSAiZ2VvX2FjY2Vzc2lvbiIpICU+JSAKICBtdXRhdGUoaW5kZXggPSA1KSAlPiUgCiAgZ2dwbG90KCkgKwogICMgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh5ID0gZmN0X3Jlb3JkZXIoZ2VvX2FjY2Vzc2lvbiwgc3YsIC5mdW4gPSBmdW5jdGlvbih4KXt4fSksIHggPSBzdiwgZmlsbCA9IHNldCkpICsKICBnZW9tX2JveHBsb3QobWFwcGluZyA9IGFlcyh4ID0gc3VibWlzc2lvbl9kYXRlLCB5ID0gc3YsIGZpbGwgPSBzZXQpKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgbGFicyh5ID0gIlN1cnJvZ2F0ZSBWYXJpYWJsZSBWYWx1ZSIsIHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBsYXRlbnQgdmFyaWFibGUgZXN0aW1hdGVkIGJ5IFNWQSBmb3IgZGlmZmVyZW50IGdyb3VwaW5nIGZhY3RvcnMiKQogIAoKZ2dzYXZlKCJwbG90cy9leHBsb3JhdGlvbl9wbG90cy9zdmFfZ3JvdXBpbmdfY2xhc3NRTi5wbmciKQpgYGAKCgojIFRyeWluZyB0byBzZWUgaWYgdGhlIFNWQSBlc3RpbWF0ZXMgYSBiYXRjaCB3aGVuIFFOIGlzIG5vdCBhcHBsaWVkCgpJbiB0aGlzIGF0dGVtcHQsIEkgcGVyZm9ybSBubyBxdWFudGlsZSBub3JtYWxpemF0aW9uIHdoaWxlIHBlcmZvcm1pbmcgUk1BLiBJZiBRTiBoYXMgbm90IGJlZW4gcGVyZm9ybWVkIGFuZCBhIHN1cnJvZ2F0ZSB2YXJpYWJsZSBzaG93cyB1cCB0aGF0IGNvcnJlc3BvbmRzIHRvIGJhdGNoLCBiYXRjaCBlZmZlY3RzIGFyZSBwcm9iYWJseSBwcmVzZW50LgoKYGBge3J9CnJhd0RhdGEuc3VtbWFyeSA8LSBybWEocmF3RGF0YSwgYmFja2dyb3VuZCA9IFRSVUUsIG5vcm1hbGl6ZSA9IEZBTFNFKQpgYGAKCgpgYGB7cn0KcmF3RGF0YS5zdW1tYXJ5X2RmX2xvbmcgPC0gcmF3RGF0YS5zdW1tYXJ5ICU+JSAKICBleHBycygpICU+JSAKICBhc190aWJibGUocm93bmFtZXMgPSAicHJvYmVJRCIpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IGFsbF9vZihjKHRuYmNfc2FtcGxlcywgbm9udG5iY19zYW1wbGVzKSksIG5hbWVzX3RvID0gInNhbXBsZV9pZCIsIAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiaW50ZW5zaXR5IikgJT4lIAogIGxlZnRfam9pbiguLCBtZGF0YV9zdWJzZXQsIGJ5ID0gYygic2FtcGxlX2lkIiA9ICJnZW9fYWNjZXNzaW9uIikpCmBgYAoKCgpgYGB7cn0KcDMgPC0gcmF3RGF0YS5zdW1tYXJ5X2RmX2xvbmcgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2JveHBsb3QobWFwcGluZyA9IGFlcyh4ID0gcmVvcmRlcihzYW1wbGVfaWQsIGFzLm51bWVyaWMoc2V0KSksIHkgPSBpbnRlbnNpdHksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gc2V0KSkgKwogIGxhYnMoeCA9ICJzYW1wbGVzIiwgCiAgICAgICB0aXRsZSA9IHN0cl93cmFwKCJTYW1wbGUtd2lzZSBsb2cyIGludGVuc2l0eSBib3hwbG90cyBpbiB0aGUgYWJzZW5jZSBvZiBRTiIsIDYwKSkgKwogIHNjYWxlX2NvbG9yX25wZygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkKCnAzCmBgYAoKCmBgYHtyfQpnZ3NhdmUoInBsb3RzL2V4cGxvcmF0aW9uX3Bsb3RzL0dTRTc2Mjc1X25vUU5fYm94cGxvdHMucG5nIiwgCiAgICAgICBwMSwgCiAgICAgICB1bml0cyA9ICJjbSIsIHdpZHRoID0gMzAsIGhlaWdodCA9IDEwKQpgYGAKCkdldHRpbmcgdGhlIG51bWJlciBvZiBzdXJyb2dhdGUgdmFyaWFibGVzIGluIHRoZSBhYnNlbmNlIG9mIHF1YW50aWxlIG5vcm1hbGl6YXRpb24uCgpgYGB7cn0Kbi5zdi5ub25vcm0gPC0gbnVtLnN2KGV4cHJzKHJhd0RhdGEuc3VtbWFyeSksIGZ1bGxfbW9kLCBtZXRob2Q9ImxlZWsiKQpgYGAKClRoZXJlIGlzIG9uZSBzdXJyb2dhdGUgdmFyaWFibGUgcHJlc2VudCBpbiB0aGUgYWJzZW5jZSBvZiBRTi4KCmBgYHtyfQpuLnN2Lm5vbm9ybQpgYGAKCgpgYGB7cn0Kc3ZvYmoubm9ub3JtIDwtIHN2YShleHBycyhyYXdEYXRhLnN1bW1hcnkpLCBtb2QgPSBmdWxsX21vZCwgbW9kMCA9IHJlZF9tb2QsIG4uc3YgPSBuLnN2Lm5vbm9ybSkKYGBgCgoKYGBge3J9CnN2X2RmLm5vbm9ybSA8LSB0aWJibGUoImdlb19hY2Nlc3Npb24iID0gY29sbmFtZXMoZXhwcnMocmF3RGF0YS5zdW1tYXJ5KSksICJzdiIgPSBzdm9iai5ub25vcm0kc3YpCgpoZWFkKHN2X2RmLm5vbm9ybSkKYGBgCgoKYGBge3J9CmxlZnRfam9pbihzdl9kZi5ub25vcm0sIG1kYXRhLCBieSA9ICJnZW9fYWNjZXNzaW9uIikgJT4lIAogIG11dGF0ZShpbmRleCA9IDUpICU+JSAKICBnZ3Bsb3QoKSArCiAgIyBnZW9tX2NvbChtYXBwaW5nID0gYWVzKHkgPSBmY3RfcmVvcmRlcihnZW9fYWNjZXNzaW9uLCBzdiwgLmZ1biA9IGZ1bmN0aW9uKHgpe3h9KSwgeCA9IHN2LCBmaWxsID0gc2V0KSkgKwogIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBzdWJtaXNzaW9uX2RhdGUsIHkgPSBzdiwgZmlsbCA9IHNldCkpICsKICB0aGVtZV9saWdodCgpICsKICBsYWJzKHkgPSAiU3Vycm9nYXRlIFZhcmlhYmxlIFZhbHVlIiwgCiAgICAgICB0aXRsZSA9IHN0cl93cmFwKCJEaXN0cmlidXRpb24gb2YgbGF0ZW50IHZhcmlhYmxlIGVzdGltYXRlZCBieSBTVkEgZm9yIGRpZmZlcmVudCBncm91cGluZyBmYWN0b3JzIiwgNjApKQoKZ2dzYXZlKCJwbG90cy9leHBsb3JhdGlvbl9wbG90cy9zdmFfZ3JvdXBpbmdfbm9RTi5wbmciKQpgYGAKCgo=